#ifndef NSTEAM

// Include C headers first to get types
#include "qjs_steam.h"
#include "jsffi.h"
#include "qjs_blob.h"

// C++ headers
#include <steam/steam_api.h>
#include <steam/steam_api_flat.h>
#include <string.h>

// Steam ID wrapper class
static JSClassID js_steamid_id;

typedef struct {
  uint64_t id;
} steamid_t;

static void js_steamid_finalizer(JSRuntime *rt, JSValue val) {
  steamid_t *sid = (steamid_t*)JS_GetOpaque(val, js_steamid_id);
  if (sid) js_free_rt(rt, sid);
}

static JSClassDef js_steamid_class = {
  .class_name = "SteamID",
  .finalizer = js_steamid_finalizer,
};

static steamid_t *js2steamid(JSContext *js, JSValue val) {
  if (JS_GetClassID(val) != js_steamid_id) return NULL;
  return (steamid_t*)JS_GetOpaque(val, js_steamid_id);
}

static JSValue steamid2js(JSContext *js, uint64_t id) {
  steamid_t *sid = (steamid_t*)js_mallocz(js, sizeof(steamid_t));
  if (!sid) return JS_ThrowOutOfMemory(js);
  
  sid->id = id;
  
  JSValue obj = JS_NewObjectClass(js, js_steamid_id);
  JS_SetOpaque(obj, sid);
  return obj;
}

static JSValue js_steamid_toString(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  steamid_t *sid = js2steamid(js, self);
  if (!sid) return JS_ThrowTypeError(js, "Expected SteamID");
  
  char buf[32];
  snprintf(buf, sizeof(buf), "%llu", (unsigned long long)sid->id);
  return JS_NewString(js, buf);
}

static JSValue js_steamid_toNumber(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  steamid_t *sid = js2steamid(js, self);
  if (!sid) return JS_ThrowTypeError(js, "Expected SteamID");
  
  // Return as a safe JS number (may lose precision for very large IDs)
  return JS_NewFloat64(js, (double)sid->id);
}

static const JSCFunctionListEntry js_steamid_proto_funcs[] = {
  JS_CFUNC_DEF("toString", 0, js_steamid_toString),
  JS_CFUNC_DEF("toNumber", 0, js_steamid_toNumber),
};

// Steam interface pointers
static ISteamUserStats *steam_stats = NULL;
static ISteamApps *steam_apps = NULL;
static ISteamRemoteStorage *steam_remote = NULL;
static ISteamUGC *steam_ugc = NULL;
static ISteamUser *steam_user = NULL;
static ISteamFriends *steam_friends = NULL;
static ISteamUtils *steam_utils = NULL;
static ISteamMatchmaking *steam_matchmaking = NULL;
static ISteamNetworking *steam_networking = NULL;
static ISteamScreenshots *steam_screenshots = NULL;
static ISteamHTTP *steam_http = NULL;
static ISteamInput *steam_input = NULL;
static ISteamInventory *steam_inventory = NULL;
static ISteamMusic *steam_music = NULL;
static bool steam_initialized = false;

extern "C" {

// STEAM INITIALIZATION
JSC_CCALL(steam_init,
  if (steam_initialized) {
    return JS_NewBool(js, true);
  }
  
  ESteamAPIInitResult init_result = SteamAPI_InitFlat(nullptr);
  
  switch (init_result) {
    case k_ESteamAPIInitResult_OK:
      steam_initialized = true;
      break;
      
    case k_ESteamAPIInitResult_NoSteamClient:
      return JS_ThrowInternalError(js, "Failed to initialize Steam: Steam client is not running or not logged in");
      
    case k_ESteamAPIInitResult_VersionMismatch:
      return JS_ThrowInternalError(js, "Failed to initialize Steam: Steam client version is out of date");
      
    case k_ESteamAPIInitResult_FailedGeneric:
    default:
      return JS_ThrowInternalError(js, "Failed to initialize Steam: Generic failure (check steam_appid.txt file exists and contains valid AppID)");
  }
  
  // Get interface pointers - must be called after SteamAPI_InitFlat succeeds
  steam_user = SteamAPI_SteamUser();
  steam_utils = SteamAPI_SteamUtils();
  steam_friends = SteamAPI_SteamFriends();
  steam_apps = SteamAPI_SteamApps();
  steam_stats = SteamAPI_SteamUserStats();
  steam_remote = SteamAPI_SteamRemoteStorage();
  steam_ugc = SteamAPI_SteamUGC();
  steam_matchmaking = SteamAPI_SteamMatchmaking();
  steam_networking = SteamAPI_SteamNetworking();
  steam_screenshots = SteamAPI_SteamScreenshots();
  steam_http = SteamAPI_SteamHTTP();
  steam_input = SteamAPI_SteamInput();
  steam_inventory = SteamAPI_SteamInventory();
  steam_music = SteamAPI_SteamMusic();
  
  
  // Verify critical interfaces were obtained
  if (!steam_user) {
    steam_initialized = false;
    SteamAPI_Shutdown();
    return JS_ThrowInternalError(js, "Failed to get Steam user interface");
  }
  
  if (!steam_utils) {
    steam_initialized = false;
    SteamAPI_Shutdown();
    return JS_ThrowInternalError(js, "Failed to get Steam utils interface");
  }
  
  if (!steam_friends) {
    steam_initialized = false;
    SteamAPI_Shutdown();
    return JS_ThrowInternalError(js, "Failed to get Steam friends interface");
  }
  
  // Verify user is logged in
  if (!SteamAPI_ISteamUser_BLoggedOn(steam_user)) {
    steam_initialized = false;
    SteamAPI_Shutdown();
    return JS_ThrowInternalError(js, "Steam user is not logged in");
  }
  
  return JS_NewBool(js, steam_initialized);
)

JSC_CCALL(steam_shutdown,
  if (steam_initialized) {
    SteamAPI_Shutdown();
    steam_initialized = false;
  }
  return JS_NULL;
)

JSC_CCALL(steam_run_callbacks,
  if (steam_initialized) {
    SteamAPI_RunCallbacks();
  }
  return JS_NULL;
)

// USER STATS & ACHIEVEMENTS
JSC_CCALL(stats_request,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  bool success = SteamAPI_ISteamUserStats_RequestUserStats(steam_stats, SteamAPI_ISteamUser_GetSteamID(steam_user));
  return JS_NewBool(js, success);
)

JSC_CCALL(stats_store,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  bool success = SteamAPI_ISteamUserStats_StoreStats(steam_stats);
  return JS_NewBool(js, success);
)

JSC_CCALL(stats_get_int,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  int32 value;
  bool success = SteamAPI_ISteamUserStats_GetStatInt32(steam_stats, name, &value);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  return JS_NewInt32(js, value);
)

JSC_CCALL(stats_get_float,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  float value;
  bool success = SteamAPI_ISteamUserStats_GetStatFloat(steam_stats, name, &value);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  return JS_NewFloat64(js, value);
)

JSC_CCALL(stats_set_int,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  int32 value;
  JS_ToInt32(js, &value, argv[1]);
  
  bool success = SteamAPI_ISteamUserStats_SetStatInt32(steam_stats, name, value);
  JS_FreeCString(js, name);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(stats_set_float,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  double dvalue;
  JS_ToFloat64(js, &dvalue, argv[1]);
  float value = (float)dvalue;
  
  bool success = SteamAPI_ISteamUserStats_SetStatFloat(steam_stats, name, value);
  JS_FreeCString(js, name);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(achievement_get,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  bool achieved;
  bool success = SteamAPI_ISteamUserStats_GetAchievement(steam_stats, name, &achieved);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  return JS_NewBool(js, achieved);
)

JSC_CCALL(achievement_set,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamUserStats_SetAchievement(steam_stats, name);
  JS_FreeCString(js, name);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(achievement_clear,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamUserStats_ClearAchievement(steam_stats, name);
  JS_FreeCString(js, name);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(achievement_count,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  uint32 count = SteamAPI_ISteamUserStats_GetNumAchievements(steam_stats);
  return JS_NewUint32(js, count);
)

JSC_CCALL(achievement_name,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  uint32 index;
  JS_ToUint32(js, &index, argv[0]);
  
  const char *name = SteamAPI_ISteamUserStats_GetAchievementName(steam_stats, index);
  if (!name) return JS_NULL;
  
  return JS_NewString(js, name);
)

JSC_CCALL(achievement_get_and_unlock_time,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  bool achieved;
  uint32 unlock_time;
  bool success = SteamAPI_ISteamUserStats_GetAchievementAndUnlockTime(steam_stats, name, &achieved, &unlock_time);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "achieved", JS_NewBool(js, achieved));
  JS_SetPropertyStr(js, obj, "unlock_time", JS_NewUint32(js, unlock_time));
  return obj;
)

JSC_CCALL(achievement_get_icon,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  int icon = SteamAPI_ISteamUserStats_GetAchievementIcon(steam_stats, name);
  JS_FreeCString(js, name);
  
  return JS_NewInt32(js, icon);
)

JSC_CCALL(achievement_get_display_attribute,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  const char *key = JS_ToCString(js, argv[1]);
  if (!key) {
    JS_FreeCString(js, name);
    return JS_EXCEPTION;
  }
  
  const char *value = SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(steam_stats, name, key);
  JS_FreeCString(js, name);
  JS_FreeCString(js, key);
  
  return JS_NewString(js, value ? value : "");
)

JSC_CCALL(achievement_indicate_progress,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  uint32 cur_progress, max_progress;
  JS_ToUint32(js, &cur_progress, argv[1]);
  JS_ToUint32(js, &max_progress, argv[2]);
  
  bool success = SteamAPI_ISteamUserStats_IndicateAchievementProgress(steam_stats, name, cur_progress, max_progress);
  JS_FreeCString(js, name);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(achievement_get_user_achievement,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  const char *name = JS_ToCString(js, argv[1]);
  if (!name) return JS_EXCEPTION;
  
  bool achieved;
  bool success = SteamAPI_ISteamUserStats_GetUserAchievement(steam_stats, sid->id, name, &achieved);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  return JS_NewBool(js, achieved);
)

JSC_CCALL(achievement_get_user_achievement_and_unlock_time,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  const char *name = JS_ToCString(js, argv[1]);
  if (!name) return JS_EXCEPTION;
  
  bool achieved;
  uint32 unlock_time;
  bool success = SteamAPI_ISteamUserStats_GetUserAchievementAndUnlockTime(steam_stats, sid->id, name, &achieved, &unlock_time);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "achieved", JS_NewBool(js, achieved));
  JS_SetPropertyStr(js, obj, "unlock_time", JS_NewUint32(js, unlock_time));
  return obj;
)

JSC_CCALL(achievement_get_achieved_percent,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  float percent;
  bool success = SteamAPI_ISteamUserStats_GetAchievementAchievedPercent(steam_stats, name, &percent);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  return JS_NewFloat64(js, percent);
)

JSC_CCALL(achievement_get_progress_limits_int,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  int32 min_progress, max_progress;
  bool success = SteamAPI_ISteamUserStats_GetAchievementProgressLimitsInt32(steam_stats, name, &min_progress, &max_progress);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "min_progress", JS_NewInt32(js, min_progress));
  JS_SetPropertyStr(js, obj, "max_progress", JS_NewInt32(js, max_progress));
  return obj;
)

JSC_CCALL(achievement_get_progress_limits_float,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  float min_progress, max_progress;
  bool success = SteamAPI_ISteamUserStats_GetAchievementProgressLimitsFloat(steam_stats, name, &min_progress, &max_progress);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "min_progress", JS_NewFloat64(js, min_progress));
  JS_SetPropertyStr(js, obj, "max_progress", JS_NewFloat64(js, max_progress));
  return obj;
)

JSC_CCALL(stats_update_avg_rate,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  double count_this_session, session_length;
  JS_ToFloat64(js, &count_this_session, argv[1]);
  JS_ToFloat64(js, &session_length, argv[2]);
  
  bool success = SteamAPI_ISteamUserStats_UpdateAvgRateStat(steam_stats, name, (float)count_this_session, session_length);
  JS_FreeCString(js, name);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(stats_get_user_stat_int,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  const char *name = JS_ToCString(js, argv[1]);
  if (!name) return JS_EXCEPTION;
  
  int32 value;
  bool success = SteamAPI_ISteamUserStats_GetUserStatInt32(steam_stats, sid->id, name, &value);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  return JS_NewInt32(js, value);
)

JSC_CCALL(stats_get_user_stat_float,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  const char *name = JS_ToCString(js, argv[1]);
  if (!name) return JS_EXCEPTION;
  
  float value;
  bool success = SteamAPI_ISteamUserStats_GetUserStatFloat(steam_stats, sid->id, name, &value);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  return JS_NewFloat64(js, value);
)

JSC_CCALL(stats_reset_all,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  bool achievements_too = JS_ToBool(js, argv[0]);
  
  bool success = SteamAPI_ISteamUserStats_ResetAllStats(steam_stats, achievements_too);
  return JS_NewBool(js, success);
)

JSC_CCALL(leaderboard_find_or_create,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  int32 sort_method, display_type;
  JS_ToInt32(js, &sort_method, argv[1]);
  JS_ToInt32(js, &display_type, argv[2]);
  
  SteamAPICall_t call = SteamAPI_ISteamUserStats_FindOrCreateLeaderboard(steam_stats, name, (ELeaderboardSortMethod)sort_method, (ELeaderboardDisplayType)display_type);
  JS_FreeCString(js, name);
  
  return steamid2js(js, call);
)

JSC_CCALL(leaderboard_find,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  SteamAPICall_t call = SteamAPI_ISteamUserStats_FindLeaderboard(steam_stats, name);
  JS_FreeCString(js, name);
  
  return steamid2js(js, call);
)

JSC_CCALL(leaderboard_get_name,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  uint64 leaderboard;
  JS_ToIndex(js, &leaderboard, argv[0]);
  
  const char *name = SteamAPI_ISteamUserStats_GetLeaderboardName(steam_stats, (SteamLeaderboard_t)leaderboard);
  return JS_NewString(js, name ? name : "");
)

JSC_CCALL(leaderboard_get_entry_count,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  uint64 leaderboard;
  JS_ToIndex(js, &leaderboard, argv[0]);
  
  int count = SteamAPI_ISteamUserStats_GetLeaderboardEntryCount(steam_stats, (SteamLeaderboard_t)leaderboard);
  return JS_NewInt32(js, count);
)

JSC_CCALL(leaderboard_get_sort_method,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  uint64 leaderboard;
  JS_ToIndex(js, &leaderboard, argv[0]);
  
  ELeaderboardSortMethod method = SteamAPI_ISteamUserStats_GetLeaderboardSortMethod(steam_stats, (SteamLeaderboard_t)leaderboard);
  return JS_NewInt32(js, method);
)

JSC_CCALL(leaderboard_get_display_type,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  uint64 leaderboard;
  JS_ToIndex(js, &leaderboard, argv[0]);
  
  ELeaderboardDisplayType type = SteamAPI_ISteamUserStats_GetLeaderboardDisplayType(steam_stats, (SteamLeaderboard_t)leaderboard);
  return JS_NewInt32(js, type);
)

JSC_CCALL(leaderboard_download_entries,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  uint64 leaderboard;
  JS_ToIndex(js, &leaderboard, argv[0]);
  
  int32 request_type, range_start, range_end;
  JS_ToInt32(js, &request_type, argv[1]);
  JS_ToInt32(js, &range_start, argv[2]);
  JS_ToInt32(js, &range_end, argv[3]);
  
  SteamAPICall_t call = SteamAPI_ISteamUserStats_DownloadLeaderboardEntries(steam_stats, (SteamLeaderboard_t)leaderboard, (ELeaderboardDataRequest)request_type, range_start, range_end);
  return steamid2js(js, call);
)

JSC_CCALL(leaderboard_download_entries_for_users,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  uint64 leaderboard;
  JS_ToIndex(js, &leaderboard, argv[0]);
  
  // For simplicity, assume single user for now - proper implementation would handle array
  steamid_t *sid = js2steamid(js, argv[1]);
  if (!sid) return JS_ThrowTypeError(js, "Second argument must be a SteamID");
  
  CSteamID user_id = CSteamID(sid->id);
  SteamAPICall_t call = SteamAPI_ISteamUserStats_DownloadLeaderboardEntriesForUsers(steam_stats, (SteamLeaderboard_t)leaderboard, &user_id, 1);
  
  return steamid2js(js, call);
)

JSC_CCALL(leaderboard_get_downloaded_entry,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  uint64 entries;
  JS_ToIndex(js, &entries, argv[0]);
  
  int32 index;
  JS_ToInt32(js, &index, argv[1]);
  
  LeaderboardEntry_t entry;
  int32 details[32]; // Max details buffer
  bool success = SteamAPI_ISteamUserStats_GetDownloadedLeaderboardEntry(steam_stats, (SteamLeaderboardEntries_t)entries, index, &entry, details, 32);
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "steam_id", steamid2js(js, entry.m_steamIDUser.ConvertToUint64()));
  JS_SetPropertyStr(js, obj, "global_rank", JS_NewInt32(js, entry.m_nGlobalRank));
  JS_SetPropertyStr(js, obj, "score", JS_NewInt32(js, entry.m_nScore));
  JS_SetPropertyStr(js, obj, "details_count", JS_NewInt32(js, entry.m_cDetails));
  return obj;
)

JSC_CCALL(leaderboard_upload_score,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  uint64 leaderboard;
  JS_ToIndex(js, &leaderboard, argv[0]);
  
  int32 upload_method, score;
  JS_ToInt32(js, &upload_method, argv[1]);
  JS_ToInt32(js, &score, argv[2]);
  
  SteamAPICall_t call = SteamAPI_ISteamUserStats_UploadLeaderboardScore(steam_stats, (SteamLeaderboard_t)leaderboard, (ELeaderboardUploadScoreMethod)upload_method, score, NULL, 0);
  return steamid2js(js, call);
)

JSC_CCALL(leaderboard_attach_ugc,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  uint64 leaderboard, ugc_handle;
  JS_ToIndex(js, &leaderboard, argv[0]);
  JS_ToIndex(js, &ugc_handle, argv[1]);
  
  SteamAPICall_t call = SteamAPI_ISteamUserStats_AttachLeaderboardUGC(steam_stats, (SteamLeaderboard_t)leaderboard, (UGCHandle_t)ugc_handle);
  return steamid2js(js, call);
)

JSC_CCALL(stats_get_number_of_current_players,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  SteamAPICall_t call = SteamAPI_ISteamUserStats_GetNumberOfCurrentPlayers(steam_stats);
  return steamid2js(js, call);
)

JSC_CCALL(stats_request_global_achievement_percentages,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  SteamAPICall_t call = SteamAPI_ISteamUserStats_RequestGlobalAchievementPercentages(steam_stats);
  return steamid2js(js, call);
)

JSC_CCALL(achievement_get_most_achieved_info,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  char name[256];
  float percent;
  bool achieved;
  
  int result = SteamAPI_ISteamUserStats_GetMostAchievedAchievementInfo(steam_stats, name, 256, &percent, &achieved);
  
  if (result <= 0) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "name", JS_NewString(js, name));
  JS_SetPropertyStr(js, obj, "percent", JS_NewFloat64(js, percent));
  JS_SetPropertyStr(js, obj, "achieved", JS_NewBool(js, achieved));
  return obj;
)

JSC_CCALL(achievement_get_next_most_achieved_info,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  int32 iterator_previous;
  JS_ToInt32(js, &iterator_previous, argv[0]);
  
  char name[256];
  float percent;
  bool achieved;
  
  int result = SteamAPI_ISteamUserStats_GetNextMostAchievedAchievementInfo(steam_stats, iterator_previous, name, 256, &percent, &achieved);
  
  if (result <= 0) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "name", JS_NewString(js, name));
  JS_SetPropertyStr(js, obj, "percent", JS_NewFloat64(js, percent));
  JS_SetPropertyStr(js, obj, "achieved", JS_NewBool(js, achieved));
  JS_SetPropertyStr(js, obj, "iterator", JS_NewInt32(js, result));
  return obj;
)

JSC_CCALL(stats_request_global,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  int32 history_days;
  JS_ToInt32(js, &history_days, argv[0]);
  
  SteamAPICall_t call = SteamAPI_ISteamUserStats_RequestGlobalStats(steam_stats, history_days);
  return steamid2js(js, call);
)

JSC_CCALL(stats_get_global_stat_int64,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  int64 value;
  bool success = SteamAPI_ISteamUserStats_GetGlobalStatInt64(steam_stats, name, &value);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  return steamid2js(js, value);
)

JSC_CCALL(stats_get_global_stat_double,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  double value;
  bool success = SteamAPI_ISteamUserStats_GetGlobalStatDouble(steam_stats, name, &value);
  JS_FreeCString(js, name);
  
  if (!success) return JS_NULL;
  return JS_NewFloat64(js, value);
)

JSC_CCALL(stats_get_global_stat_history_int64,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  int64 data[100]; // Buffer for history data
  int32 result = SteamAPI_ISteamUserStats_GetGlobalStatHistoryInt64(steam_stats, name, data, sizeof(data));
  JS_FreeCString(js, name);
  
  if (result <= 0) return JS_NULL;
  
  JSValue arr = JS_NewArray(js);
  for (int i = 0; i < result; i++) {
    JS_SetPropertyUint32(js, arr, i, steamid2js(js, data[i]));
  }
  return arr;
)

JSC_CCALL(stats_get_global_stat_history_double,
  if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized");
  
  const char *name = JS_ToCString(js, argv[0]);
  if (!name) return JS_EXCEPTION;
  
  double data[100]; // Buffer for history data
  int32 result = SteamAPI_ISteamUserStats_GetGlobalStatHistoryDouble(steam_stats, name, data, sizeof(data));
  JS_FreeCString(js, name);
  
  if (result <= 0) return JS_NULL;
  
  JSValue arr = JS_NewArray(js);
  for (int i = 0; i < result; i++) {
    JS_SetPropertyUint32(js, arr, i, JS_NewFloat64(js, data[i]));
  }
  return arr;
)

// APPS
JSC_CCALL(app_id,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  AppId_t appid = SteamAPI_ISteamUtils_GetAppID(steam_utils);
  return JS_NewUint32(js, appid);
)

JSC_CCALL(app_owner,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  uint64_steamid owner = SteamAPI_ISteamApps_GetAppOwner(steam_apps);
  return steamid2js(js, owner);
)

JSC_CCALL(app_installed,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  uint32 appid;
  JS_ToUint32(js, &appid, argv[0]);
  
  bool installed = SteamAPI_ISteamApps_BIsAppInstalled(steam_apps, appid);
  return JS_NewBool(js, installed);
)

JSC_CCALL(app_subscribed,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  bool subscribed = SteamAPI_ISteamApps_BIsSubscribed(steam_apps);
  return JS_NewBool(js, subscribed);
)

JSC_CCALL(app_language,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  const char *lang = SteamAPI_ISteamApps_GetCurrentGameLanguage(steam_apps);
  return JS_NewString(js, lang ? lang : "english");
)

JSC_CCALL(app_dlc_installed,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  AppId_t dlcid;
  JS_ToUint32(js, &dlcid, argv[0]);
  
  bool installed = SteamAPI_ISteamApps_BIsDlcInstalled(steam_apps, dlcid);
  return JS_NewBool(js, installed);
)

JSC_CCALL(app_is_low_violence,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  bool low_violence = SteamAPI_ISteamApps_BIsLowViolence(steam_apps);
  return JS_NewBool(js, low_violence);
)

JSC_CCALL(app_is_cybercafe,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  bool cybercafe = SteamAPI_ISteamApps_BIsCybercafe(steam_apps);
  return JS_NewBool(js, cybercafe);
)

JSC_CCALL(app_is_vac_banned,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  bool vac_banned = SteamAPI_ISteamApps_BIsVACBanned(steam_apps);
  return JS_NewBool(js, vac_banned);
)

JSC_CCALL(app_available_languages,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  const char *languages = SteamAPI_ISteamApps_GetAvailableGameLanguages(steam_apps);
  return JS_NewString(js, languages ? languages : "");
)

JSC_CCALL(app_is_subscribed_app,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  AppId_t appid;
  JS_ToUint32(js, &appid, argv[0]);
  
  bool subscribed = SteamAPI_ISteamApps_BIsSubscribedApp(steam_apps, appid);
  return JS_NewBool(js, subscribed);
)

JSC_CCALL(app_earliest_purchase_time,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  AppId_t appid;
  JS_ToUint32(js, &appid, argv[0]);
  
  uint32 time = SteamAPI_ISteamApps_GetEarliestPurchaseUnixTime(steam_apps, appid);
  return JS_NewUint32(js, time);
)

JSC_CCALL(app_is_subscribed_from_free_weekend,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  bool free_weekend = SteamAPI_ISteamApps_BIsSubscribedFromFreeWeekend(steam_apps);
  return JS_NewBool(js, free_weekend);
)

JSC_CCALL(app_dlc_count,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  int count = SteamAPI_ISteamApps_GetDLCCount(steam_apps);
  return JS_NewInt32(js, count);
)

JSC_CCALL(app_get_dlc_data_by_index,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  int index;
  JS_ToInt32(js, &index, argv[0]);
  
  AppId_t appid;
  bool available;
  char name[256];
  
  bool success = SteamAPI_ISteamApps_BGetDLCDataByIndex(steam_apps, index, &appid, &available, name, sizeof(name));
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "appid", JS_NewUint32(js, appid));
  JS_SetPropertyStr(js, obj, "available", JS_NewBool(js, available));
  JS_SetPropertyStr(js, obj, "name", JS_NewString(js, name));
  return obj;
)

JSC_CCALL(app_install_dlc,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  AppId_t appid;
  JS_ToUint32(js, &appid, argv[0]);
  
  SteamAPI_ISteamApps_InstallDLC(steam_apps, appid);
  return JS_NULL;
)

JSC_CCALL(app_uninstall_dlc,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  AppId_t appid;
  JS_ToUint32(js, &appid, argv[0]);
  
  SteamAPI_ISteamApps_UninstallDLC(steam_apps, appid);
  return JS_NULL;
)

JSC_CCALL(app_request_proof_of_purchase_key,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  AppId_t appid;
  JS_ToUint32(js, &appid, argv[0]);
  
  SteamAPI_ISteamApps_RequestAppProofOfPurchaseKey(steam_apps, appid);
  return JS_NULL;
)

JSC_CCALL(app_get_current_beta_name,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  char beta_name[256];
  bool has_beta = SteamAPI_ISteamApps_GetCurrentBetaName(steam_apps, beta_name, sizeof(beta_name));
  
  if (!has_beta) return JS_NULL;
  return JS_NewString(js, beta_name);
)

JSC_CCALL(app_mark_content_corrupt,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  bool missing_files_only = false;
  if (argc > 0) {
    missing_files_only = JS_ToBool(js, argv[0]);
  }
  
  bool success = SteamAPI_ISteamApps_MarkContentCorrupt(steam_apps, missing_files_only);
  return JS_NewBool(js, success);
)

JSC_CCALL(app_get_installed_depots,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  AppId_t appid;
  JS_ToUint32(js, &appid, argv[0]);
  
  const uint32 max_depots = 32;
  DepotId_t depots[max_depots];
  
  uint32 count = SteamAPI_ISteamApps_GetInstalledDepots(steam_apps, appid, depots, max_depots);
  
  JSValue arr = JS_NewArray(js);
  for (uint32 i = 0; i < count; i++) {
    JS_SetPropertyUint32(js, arr, i, JS_NewUint32(js, depots[i]));
  }
  return arr;
)

JSC_CCALL(app_get_install_dir,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  AppId_t appid;
  JS_ToUint32(js, &appid, argv[0]);
  
  char folder[512];
  uint32 size = SteamAPI_ISteamApps_GetAppInstallDir(steam_apps, appid, folder, sizeof(folder));
  
  if (size == 0) return JS_NULL;
  return JS_NewString(js, folder);
)

JSC_CCALL(app_get_launch_query_param,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  const char *key = JS_ToCString(js, argv[0]);
  if (!key) return JS_ThrowTypeError(js, "Expected string parameter");
  
  const char *value = SteamAPI_ISteamApps_GetLaunchQueryParam(steam_apps, key);
  JS_FreeCString(js, key);
  
  if (!value) return JS_NULL;
  return JS_NewString(js, value);
)

JSC_CCALL(app_get_dlc_download_progress,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  AppId_t appid;
  JS_ToUint32(js, &appid, argv[0]);
  
  uint64 bytes_downloaded, bytes_total;
  bool success = SteamAPI_ISteamApps_GetDlcDownloadProgress(steam_apps, appid, &bytes_downloaded, &bytes_total);
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "downloaded", steamid2js(js, bytes_downloaded));
  JS_SetPropertyStr(js, obj, "total", steamid2js(js, bytes_total));
  return obj;
)

JSC_CCALL(app_get_build_id,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  int build_id = SteamAPI_ISteamApps_GetAppBuildId(steam_apps);
  return JS_NewInt32(js, build_id);
)

JSC_CCALL(app_request_all_proof_of_purchase_keys,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  SteamAPI_ISteamApps_RequestAllProofOfPurchaseKeys(steam_apps);
  return JS_NULL;
)

JSC_CCALL(app_get_file_details,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  const char *filename = JS_ToCString(js, argv[0]);
  if (!filename) return JS_ThrowTypeError(js, "Expected string parameter");
  
  SteamAPICall_t call = SteamAPI_ISteamApps_GetFileDetails(steam_apps, filename);
  JS_FreeCString(js, filename);
  
  return steamid2js(js, call);
)

JSC_CCALL(app_get_launch_command_line,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  char command_line[1024];
  int size = SteamAPI_ISteamApps_GetLaunchCommandLine(steam_apps, command_line, sizeof(command_line));
  
  if (size <= 0) return JS_NULL;
  return JS_NewString(js, command_line);
)

JSC_CCALL(app_is_subscribed_from_family_sharing,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  bool family_sharing = SteamAPI_ISteamApps_BIsSubscribedFromFamilySharing(steam_apps);
  return JS_NewBool(js, family_sharing);
)

JSC_CCALL(app_is_timed_trial,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  uint32 seconds_allowed, seconds_played;
  bool is_trial = SteamAPI_ISteamApps_BIsTimedTrial(steam_apps, &seconds_allowed, &seconds_played);
  
  if (!is_trial) return JS_NewBool(js, false);
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "is_trial", JS_NewBool(js, true));
  JS_SetPropertyStr(js, obj, "seconds_allowed", JS_NewUint32(js, seconds_allowed));
  JS_SetPropertyStr(js, obj, "seconds_played", JS_NewUint32(js, seconds_played));
  return obj;
)

JSC_CCALL(app_set_dlc_context,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  AppId_t appid;
  JS_ToUint32(js, &appid, argv[0]);
  
  bool success = SteamAPI_ISteamApps_SetDlcContext(steam_apps, appid);
  return JS_NewBool(js, success);
)

JSC_CCALL(app_get_num_betas,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  int available, private_betas;
  int total = SteamAPI_ISteamApps_GetNumBetas(steam_apps, &available, &private_betas);
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "total", JS_NewInt32(js, total));
  JS_SetPropertyStr(js, obj, "available", JS_NewInt32(js, available));
  JS_SetPropertyStr(js, obj, "private", JS_NewInt32(js, private_betas));
  return obj;
)

JSC_CCALL(app_get_beta_info,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  int beta_index;
  JS_ToInt32(js, &beta_index, argv[0]);
  
  uint32 flags, build_id;
  char beta_name[128], description[512];
  
  bool success = SteamAPI_ISteamApps_GetBetaInfo(steam_apps, beta_index, &flags, &build_id, 
                                                 beta_name, sizeof(beta_name), 
                                                 description, sizeof(description));
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "flags", JS_NewUint32(js, flags));
  JS_SetPropertyStr(js, obj, "build_id", JS_NewUint32(js, build_id));
  JS_SetPropertyStr(js, obj, "name", JS_NewString(js, beta_name));
  JS_SetPropertyStr(js, obj, "description", JS_NewString(js, description));
  return obj;
)

JSC_CCALL(app_set_active_beta,
  if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized");
  
  const char *beta_name = JS_ToCString(js, argv[0]);
  if (!beta_name) return JS_ThrowTypeError(js, "Expected string parameter");
  
  bool success = SteamAPI_ISteamApps_SetActiveBeta(steam_apps, beta_name);
  JS_FreeCString(js, beta_name);
  
  return JS_NewBool(js, success);
)

// USER
JSC_CCALL(user_logged_on,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  bool logged = SteamAPI_ISteamUser_BLoggedOn(steam_user);
  return JS_NewBool(js, logged);
)

JSC_CCALL(user_steam_id,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  uint64_steamid id = SteamAPI_ISteamUser_GetSteamID(steam_user);
  return steamid2js(js, id);
)

JSC_CCALL(user_level,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  int level = SteamAPI_ISteamUser_GetPlayerSteamLevel(steam_user);
  return JS_NewInt32(js, level);
)

JSC_CCALL(user_hsteam_user,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  HSteamUser user = SteamAPI_ISteamUser_GetHSteamUser(steam_user);
  return JS_NewUint32(js, user);
)

JSC_CCALL(user_get_user_data_folder,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  char buffer[1024];
  bool success = SteamAPI_ISteamUser_GetUserDataFolder(steam_user, buffer, sizeof(buffer));
  
  if (!success) return JS_NULL;
  return JS_NewString(js, buffer);
)

JSC_CCALL(user_start_voice_recording,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  SteamAPI_ISteamUser_StartVoiceRecording(steam_user);
  return JS_NULL;
)

JSC_CCALL(user_stop_voice_recording,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  SteamAPI_ISteamUser_StopVoiceRecording(steam_user);
  return JS_NULL;
)

JSC_CCALL(user_get_available_voice,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  uint32 compressed, uncompressed_deprecated;
  uint32 sample_rate = 11025;
  if (argc > 0) JS_ToUint32(js, &sample_rate, argv[0]);
  
  EVoiceResult result = SteamAPI_ISteamUser_GetAvailableVoice(steam_user, &compressed, &uncompressed_deprecated, sample_rate);
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "result", JS_NewInt32(js, result));
  JS_SetPropertyStr(js, obj, "compressed_bytes", JS_NewUint32(js, compressed));
  return obj;
)

JSC_CCALL(user_get_voice,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  uint32 buffer_size = 8192;
  if (argc > 0) JS_ToUint32(js, &buffer_size, argv[0]);
  
  void *buffer = js_malloc(js, buffer_size);
  if (!buffer) return JS_ThrowOutOfMemory(js);
  
  uint32 bytes_written;
  EVoiceResult result = SteamAPI_ISteamUser_GetVoice(steam_user, true, buffer, buffer_size, &bytes_written, false, NULL, 0, NULL, 0);
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "result", JS_NewInt32(js, result));
  
  if (result == k_EVoiceResultOK && bytes_written > 0) {
    JSValue blob = js_new_blob_stoned_copy(js, buffer, bytes_written);
    JS_SetPropertyStr(js, obj, "data", blob);
    JS_SetPropertyStr(js, obj, "bytes_written", JS_NewUint32(js, bytes_written));
  }
  
  js_free(js, buffer);
  return obj;
)

JSC_CCALL(user_decompress_voice,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  size_t compressed_size;
  void *compressed_data = js_get_blob_data(js, &compressed_size, argv[0]);
  if (!compressed_data) return JS_ThrowTypeError(js, "First argument must be a blob");
  
  uint32 dest_buffer_size = 22050 * 2;
  uint32 sample_rate = 22050;
  if (argc > 1) JS_ToUint32(js, &dest_buffer_size, argv[1]);
  if (argc > 2) JS_ToUint32(js, &sample_rate, argv[2]);
  
  void *dest_buffer = js_malloc(js, dest_buffer_size);
  if (!dest_buffer) return JS_ThrowOutOfMemory(js);
  
  uint32 bytes_written;
  EVoiceResult result = SteamAPI_ISteamUser_DecompressVoice(steam_user, compressed_data, compressed_size, dest_buffer, dest_buffer_size, &bytes_written, sample_rate);
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "result", JS_NewInt32(js, result));
  
  if (result == k_EVoiceResultOK && bytes_written > 0) {
    JSValue blob = js_new_blob_stoned_copy(js, dest_buffer, bytes_written);
    JS_SetPropertyStr(js, obj, "data", blob);
    JS_SetPropertyStr(js, obj, "bytes_written", JS_NewUint32(js, bytes_written));
  }
  
  js_free(js, dest_buffer);
  return obj;
)

JSC_CCALL(user_get_voice_optimal_sample_rate,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  uint32 rate = SteamAPI_ISteamUser_GetVoiceOptimalSampleRate(steam_user);
  return JS_NewUint32(js, rate);
)

JSC_CCALL(user_get_auth_session_ticket,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  uint8_t ticket[1024];
  uint32 ticket_size;
  
  HAuthTicket auth_ticket = SteamAPI_ISteamUser_GetAuthSessionTicket(steam_user, ticket, sizeof(ticket), &ticket_size, NULL);
  
  if (auth_ticket == k_HAuthTicketInvalid) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "auth_ticket", JS_NewUint32(js, auth_ticket));
  
  if (ticket_size > 0) {
    JSValue blob = js_new_blob_stoned_copy(js, ticket, ticket_size);
    JS_SetPropertyStr(js, obj, "ticket_data", blob);
    JS_SetPropertyStr(js, obj, "ticket_size", JS_NewUint32(js, ticket_size));
  }
  
  return obj;
)

JSC_CCALL(user_get_auth_ticket_for_web_api,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  const char *identity = JS_ToCString(js, argv[0]);
  if (!identity) return JS_EXCEPTION;
  
  HAuthTicket auth_ticket = SteamAPI_ISteamUser_GetAuthTicketForWebApi(steam_user, identity);
  JS_FreeCString(js, identity);
  
  if (auth_ticket == k_HAuthTicketInvalid) return JS_NULL;
  return JS_NewUint32(js, auth_ticket);
)

JSC_CCALL(user_begin_auth_session,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  size_t ticket_size;
  void *ticket_data = js_get_blob_data(js, &ticket_size, argv[0]);
  if (!ticket_data) return JS_ThrowTypeError(js, "First argument must be a blob");
  
  steamid_t *sid = js2steamid(js, argv[1]);
  if (!sid) return JS_ThrowTypeError(js, "Second argument must be a SteamID");
  
  EBeginAuthSessionResult result = SteamAPI_ISteamUser_BeginAuthSession(steam_user, ticket_data, ticket_size, sid->id);
  return JS_NewInt32(js, result);
)

JSC_CCALL(user_end_auth_session,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  SteamAPI_ISteamUser_EndAuthSession(steam_user, sid->id);
  return JS_NULL;
)

JSC_CCALL(user_cancel_auth_ticket,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  uint32 auth_ticket;
  JS_ToUint32(js, &auth_ticket, argv[0]);
  
  SteamAPI_ISteamUser_CancelAuthTicket(steam_user, auth_ticket);
  return JS_NULL;
)

JSC_CCALL(user_has_license_for_app,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  uint32 app_id;
  JS_ToUint32(js, &app_id, argv[1]);
  
  EUserHasLicenseForAppResult result = SteamAPI_ISteamUser_UserHasLicenseForApp(steam_user, sid->id, app_id);
  return JS_NewInt32(js, result);
)

JSC_CCALL(user_is_behind_nat,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  bool behind_nat = SteamAPI_ISteamUser_BIsBehindNAT(steam_user);
  return JS_NewBool(js, behind_nat);
)

JSC_CCALL(user_advertise_game,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  uint32 ip_server, port_server;
  JS_ToUint32(js, &ip_server, argv[1]);
  JS_ToUint32(js, &port_server, argv[2]);
  
  SteamAPI_ISteamUser_AdvertiseGame(steam_user, sid->id, ip_server, (uint16)port_server);
  return JS_NULL;
)

JSC_CCALL(user_request_encrypted_app_ticket,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  size_t data_size = 0;
  void *data = NULL;
  
  if (argc > 0 && !JS_IsNull(argv[0])) {
    data = js_get_blob_data(js, &data_size, argv[0]);
    if (!data) return JS_ThrowTypeError(js, "First argument must be a blob or null");
  }
  
  SteamAPICall_t call = SteamAPI_ISteamUser_RequestEncryptedAppTicket(steam_user, data, data_size);
  return steamid2js(js, call);
)

JSC_CCALL(user_get_encrypted_app_ticket,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  uint8_t ticket[1024];
  uint32 ticket_size;
  
  bool success = SteamAPI_ISteamUser_GetEncryptedAppTicket(steam_user, ticket, sizeof(ticket), &ticket_size);
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  if (ticket_size > 0) {
    JSValue blob = js_new_blob_stoned_copy(js, ticket, ticket_size);
    JS_SetPropertyStr(js, obj, "ticket_data", blob);
    JS_SetPropertyStr(js, obj, "ticket_size", JS_NewUint32(js, ticket_size));
  }
  
  return obj;
)

JSC_CCALL(user_get_game_badge_level,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  int32 series;
  JS_ToInt32(js, &series, argv[0]);
  
  bool foil = argc > 1 ? JS_ToBool(js, argv[1]) : false;
  
  int level = SteamAPI_ISteamUser_GetGameBadgeLevel(steam_user, series, foil);
  return JS_NewInt32(js, level);
)

JSC_CCALL(user_request_store_auth_url,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  const char *redirect_url = JS_ToCString(js, argv[0]);
  if (!redirect_url) return JS_EXCEPTION;
  
  SteamAPICall_t call = SteamAPI_ISteamUser_RequestStoreAuthURL(steam_user, redirect_url);
  JS_FreeCString(js, redirect_url);
  
  return steamid2js(js, call);
)

JSC_CCALL(user_is_phone_verified,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  bool verified = SteamAPI_ISteamUser_BIsPhoneVerified(steam_user);
  return JS_NewBool(js, verified);
)

JSC_CCALL(user_is_two_factor_enabled,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  bool enabled = SteamAPI_ISteamUser_BIsTwoFactorEnabled(steam_user);
  return JS_NewBool(js, enabled);
)

JSC_CCALL(user_is_phone_identifying,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  bool identifying = SteamAPI_ISteamUser_BIsPhoneIdentifying(steam_user);
  return JS_NewBool(js, identifying);
)

JSC_CCALL(user_is_phone_requiring_verification,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  bool requiring = SteamAPI_ISteamUser_BIsPhoneRequiringVerification(steam_user);
  return JS_NewBool(js, requiring);
)

JSC_CCALL(user_get_market_eligibility,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  SteamAPICall_t call = SteamAPI_ISteamUser_GetMarketEligibility(steam_user);
  return steamid2js(js, call);
)

JSC_CCALL(user_get_duration_control,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  SteamAPICall_t call = SteamAPI_ISteamUser_GetDurationControl(steam_user);
  return steamid2js(js, call);
)

JSC_CCALL(user_set_duration_control_online_state,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  int32 state;
  JS_ToInt32(js, &state, argv[0]);
  
  bool success = SteamAPI_ISteamUser_BSetDurationControlOnlineState(steam_user, (EDurationControlOnlineState)state);
  return JS_NewBool(js, success);
)

JSC_CCALL(user_track_app_usage_event,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  uint64 game_id;
  JS_ToIndex(js, &game_id, argv[0]);
  
  int32 app_usage_event;
  JS_ToInt32(js, &app_usage_event, argv[1]);
  
  const char *extra_info = argc > 2 ? JS_ToCString(js, argv[2]) : "";
  
  SteamAPI_ISteamUser_TrackAppUsageEvent(steam_user, game_id, app_usage_event, extra_info);
  
  if (argc > 2) JS_FreeCString(js, extra_info);
  return JS_NULL;
)

JSC_CCALL(user_initiate_game_connection_deprecated,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  steamid_t *server_sid = js2steamid(js, argv[0]);
  if (!server_sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  uint32 ip_server, port_server;
  JS_ToUint32(js, &ip_server, argv[1]);
  JS_ToUint32(js, &port_server, argv[2]);
  
  bool secure = argc > 3 ? JS_ToBool(js, argv[3]) : false;
  
  uint8_t auth_blob[1024];
  int result = SteamAPI_ISteamUser_InitiateGameConnection_DEPRECATED(steam_user, auth_blob, sizeof(auth_blob), server_sid->id, ip_server, (uint16)port_server, secure);
  
  if (result <= 0) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JSValue blob = js_new_blob_stoned_copy(js, auth_blob, result);
  JS_SetPropertyStr(js, obj, "auth_blob", blob);
  JS_SetPropertyStr(js, obj, "size", JS_NewInt32(js, result));
  
  return obj;
)

JSC_CCALL(user_terminate_game_connection_deprecated,
  if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized");
  
  uint32 ip_server, port_server;
  JS_ToUint32(js, &ip_server, argv[0]);
  JS_ToUint32(js, &port_server, argv[1]);
  
  SteamAPI_ISteamUser_TerminateGameConnection_DEPRECATED(steam_user, ip_server, (uint16)port_server);
  return JS_NULL;
)

// FRIENDS
JSC_CCALL(friends_name,
  if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized");
  
  const char *name = SteamAPI_ISteamFriends_GetPersonaName(steam_friends);
  return JS_NewString(js, name ? name : "");
)

JSC_CCALL(friends_state,
  if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized");
  
  EPersonaState state = SteamAPI_ISteamFriends_GetPersonaState(steam_friends);
  const char *state_str;
  switch(state) {
    case k_EPersonaStateOffline: state_str = "offline"; break;
    case k_EPersonaStateOnline: state_str = "online"; break;
    case k_EPersonaStateBusy: state_str = "busy"; break;
    case k_EPersonaStateAway: state_str = "away"; break;
    case k_EPersonaStateSnooze: state_str = "snooze"; break;
    case k_EPersonaStateLookingToTrade: state_str = "trade"; break;
    case k_EPersonaStateLookingToPlay: state_str = "play"; break;
    default: state_str = "unknown"; break;
  }
  return JS_NewString(js, state_str);
)

JSC_CCALL(friends_count,
  if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized");
  
  int flags = k_EFriendFlagImmediate;
  if (argc > 0) {
    JS_ToInt32(js, &flags, argv[0]);
  }
  
  int count = SteamAPI_ISteamFriends_GetFriendCount(steam_friends, flags);
  return JS_NewInt32(js, count);
)

JSC_CCALL(friends_get_friend,
  if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized");
  
  int index;
  JS_ToInt32(js, &index, argv[0]);
  
  int flags = k_EFriendFlagImmediate;
  if (argc > 1) {
    JS_ToInt32(js, &flags, argv[1]);
  }
  
  uint64_steamid friendID = SteamAPI_ISteamFriends_GetFriendByIndex(steam_friends, index, flags);
  if (friendID == 0) return JS_NULL;
  
  return steamid2js(js, friendID);
)

JSC_CCALL(friends_get_friend_name,
  if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized");
  
  uint64_steamid friendID;
  if (argc > 0 && JS_IsObject(argv[0])) {
    steamid_t *sid = js2steamid(js, argv[0]);
    if (!sid) return JS_ThrowTypeError(js, "Expected SteamID");
    friendID = sid->id;
  } else {
    double val;
    JS_ToFloat64(js, &val, argv[0]);
    friendID = (uint64_steamid)val;
  }
  
  const char *name = SteamAPI_ISteamFriends_GetFriendPersonaName(steam_friends, friendID);
  return JS_NewString(js, name ? name : "");
)

JSC_CCALL(friends_activate_overlay,
  if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized");
  
  const char *dialog = JS_ToCString(js, argv[0]);
  if (!dialog) return JS_EXCEPTION;
  
  SteamAPI_ISteamFriends_ActivateGameOverlay(steam_friends, dialog);
  JS_FreeCString(js, dialog);
  
  return JS_NULL;
)

JSC_CCALL(friends_activate_overlay_to_user,
  if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized");
  
  const char *dialog = JS_ToCString(js, argv[0]);
  if (!dialog) return JS_EXCEPTION;
  
  uint64_steamid userID;
  if (argc > 1 && JS_IsObject(argv[1])) {
    steamid_t *sid = js2steamid(js, argv[1]);
    if (!sid) {
      JS_FreeCString(js, dialog);
      return JS_ThrowTypeError(js, "Expected SteamID");
    }
    userID = sid->id;
  } else {
    double val;
    JS_ToFloat64(js, &val, argv[1]);
    userID = (uint64_steamid)val;
  }
  
  SteamAPI_ISteamFriends_ActivateGameOverlayToUser(steam_friends, dialog, userID);
  JS_FreeCString(js, dialog);
  
  return JS_NULL;
)

JSC_CCALL(friends_activate_overlay_to_web,
  if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized");
  
  const char *url = JS_ToCString(js, argv[0]);
  if (!url) return JS_EXCEPTION;
  
  SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage(steam_friends, url, k_EActivateGameOverlayToWebPageMode_Default);
  JS_FreeCString(js, url);
  
  return JS_NULL;
)

JSC_CCALL(friends_set_rich_presence,
  if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized");
  
  const char *key = JS_ToCString(js, argv[0]);
  if (!key) return JS_EXCEPTION;
  
  const char *value = JS_ToCString(js, argv[1]);
  if (!value) {
    JS_FreeCString(js, key);
    return JS_EXCEPTION;
  }
  
  bool success = SteamAPI_ISteamFriends_SetRichPresence(steam_friends, key, value);
  
  JS_FreeCString(js, key);
  JS_FreeCString(js, value);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(friends_clear_rich_presence,
  if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized");
  
  SteamAPI_ISteamFriends_ClearRichPresence(steam_friends);
  return JS_NULL;
)

// CLOUD STORAGE
JSC_CCALL(cloud_enabled_app,
  if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized");
  
  bool enabled = SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp(steam_remote);
  return JS_NewBool(js, enabled);
)

JSC_CCALL(cloud_enabled_account,
  if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized");
  
  bool enabled = SteamAPI_ISteamRemoteStorage_IsCloudEnabledForAccount(steam_remote);
  return JS_NewBool(js, enabled);
)

JSC_CCALL(cloud_enable,
  if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized");
  
  bool enable = JS_ToBool(js, argv[0]);
  SteamAPI_ISteamRemoteStorage_SetCloudEnabledForApp(steam_remote, enable);
  return JS_NULL;
)

JSC_CCALL(cloud_quota,
  if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized");
  
  uint64 total, available;
  bool success = SteamAPI_ISteamRemoteStorage_GetQuota(steam_remote, &total, &available);
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "total", JS_NewFloat64(js, (double)total));
  JS_SetPropertyStr(js, obj, "available", JS_NewFloat64(js, (double)available));
  return obj;
)

JSC_CCALL(cloud_write,
  if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized");
  
  const char *filename = JS_ToCString(js, argv[0]);
  if (!filename) return JS_EXCEPTION;
  
  size_t data_len;
  uint8_t *data = NULL;
  
  if (JS_IsString(argv[1])) {
    const char *str = JS_ToCStringLen(js, &data_len, argv[1]);
    if (!str) {
      JS_FreeCString(js, filename);
      return JS_EXCEPTION;
    }
    data = (uint8_t*)str;
  } else {
    data = (uint8_t*)js_get_blob_data(js, &data_len, argv[1]);
    if (!data) {
      JS_FreeCString(js, filename);
      return JS_ThrowTypeError(js, "Second argument must be string or ArrayBuffer");
    }
  }
  
  bool success = SteamAPI_ISteamRemoteStorage_FileWrite(steam_remote, filename, data, data_len);
  
  if (JS_IsString(argv[1])) {
    JS_FreeCString(js, (const char*)data);
  }
  JS_FreeCString(js, filename);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(cloud_read,
  if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized");
  
  const char *filename = JS_ToCString(js, argv[0]);
  if (!filename) return JS_EXCEPTION;
  
  int32 size = SteamAPI_ISteamRemoteStorage_GetFileSize(steam_remote, filename);
  if (size <= 0) {
    JS_FreeCString(js, filename);
    return JS_NULL;
  }
  
  uint8_t *buffer = (uint8_t*)js_malloc(js, size);
  if (!buffer) {
    JS_FreeCString(js, filename);
    return JS_EXCEPTION;
  }
  
  int32 read = SteamAPI_ISteamRemoteStorage_FileRead(steam_remote, filename, buffer, size);
  JS_FreeCString(js, filename);
  
  if (read != size) {
    js_free(js, buffer);
    return JS_NULL;
  }
  
  JSValue result = js_new_blob_stoned_copy(js, buffer, size);
  js_free(js, buffer);
  return result;
)

JSC_CCALL(cloud_delete,
  if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized");
  
  const char *filename = JS_ToCString(js, argv[0]);
  if (!filename) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamRemoteStorage_FileDelete(steam_remote, filename);
  JS_FreeCString(js, filename);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(cloud_exists,
  if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized");
  
  const char *filename = JS_ToCString(js, argv[0]);
  if (!filename) return JS_EXCEPTION;
  
  bool exists = SteamAPI_ISteamRemoteStorage_FileExists(steam_remote, filename);
  JS_FreeCString(js, filename);
  
  return JS_NewBool(js, exists);
)

// EXPANDED STEAM UTILS
JSC_CCALL(utils_overlay_enabled,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  bool enabled = SteamAPI_ISteamUtils_IsOverlayEnabled(steam_utils);
  return JS_NewBool(js, enabled);
)

JSC_CCALL(utils_big_picture_mode,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  bool bigPicture = SteamAPI_ISteamUtils_IsSteamInBigPictureMode(steam_utils);
  return JS_NewBool(js, bigPicture);
)

JSC_CCALL(utils_vr_mode,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  bool vrMode = SteamAPI_ISteamUtils_IsSteamRunningInVR(steam_utils);
  return JS_NewBool(js, vrMode);
)

JSC_CCALL(utils_steam_deck,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  bool steamDeck = SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck(steam_utils);
  return JS_NewBool(js, steamDeck);
)

JSC_CCALL(utils_battery_power,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint8 battery = SteamAPI_ISteamUtils_GetCurrentBatteryPower(steam_utils);
  return JS_NewUint32(js, battery);
)

JSC_CCALL(utils_seconds_since_app_active,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint32 seconds = SteamAPI_ISteamUtils_GetSecondsSinceAppActive(steam_utils);
  return JS_NewUint32(js, seconds);
)

JSC_CCALL(utils_ip_country,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  const char *country = SteamAPI_ISteamUtils_GetIPCountry(steam_utils);
  return JS_NewString(js, country ? country : "");
)

JSC_CCALL(utils_ui_language,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  const char *language = SteamAPI_ISteamUtils_GetSteamUILanguage(steam_utils);
  return JS_NewString(js, language ? language : "english");
)

JSC_CCALL(utils_get_seconds_since_computer_active,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint32 seconds = SteamAPI_ISteamUtils_GetSecondsSinceComputerActive(steam_utils);
  return JS_NewUint32(js, seconds);
)

JSC_CCALL(utils_get_connected_universe,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  EUniverse universe = SteamAPI_ISteamUtils_GetConnectedUniverse(steam_utils);
  return JS_NewUint32(js, universe);
)

JSC_CCALL(utils_get_server_real_time,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint32 server_time = SteamAPI_ISteamUtils_GetServerRealTime(steam_utils);
  return JS_NewUint32(js, server_time);
)

JSC_CCALL(utils_get_image_size,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  int image_handle;
  JS_ToInt32(js, &image_handle, argv[0]);
  
  uint32 width, height;
  bool success = SteamAPI_ISteamUtils_GetImageSize(steam_utils, image_handle, &width, &height);
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "width", JS_NewUint32(js, width));
  JS_SetPropertyStr(js, obj, "height", JS_NewUint32(js, height));
  return obj;
)

JSC_CCALL(utils_get_image_rgba,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  int image_handle;
  JS_ToInt32(js, &image_handle, argv[0]);
  
  // First get the image size to allocate buffer
  uint32 width, height;
  if (!SteamAPI_ISteamUtils_GetImageSize(steam_utils, image_handle, &width, &height)) {
    return JS_NULL;
  }
  
  uint32 buffer_size = width * height * 4; // RGBA = 4 bytes per pixel
  uint8_t *buffer = (uint8_t*)js_malloc(js, buffer_size);
  if (!buffer) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamUtils_GetImageRGBA(steam_utils, image_handle, buffer, buffer_size);
  
  if (!success) {
    js_free(js, buffer);
    return JS_NULL;
  }
  
  JSValue result = js_new_blob_stoned_copy(js, buffer, buffer_size);
  js_free(js, buffer);
  return result;
)

JSC_CCALL(utils_set_overlay_notification_position,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint32 position;
  JS_ToUint32(js, &position, argv[0]);
  
  SteamAPI_ISteamUtils_SetOverlayNotificationPosition(steam_utils, (ENotificationPosition)position);
  return JS_NULL;
)

JSC_CCALL(utils_is_api_call_completed,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  double call_double;
  JS_ToFloat64(js, &call_double, argv[0]);
  SteamAPICall_t call = (SteamAPICall_t)call_double;
  
  bool failed;
  bool completed = SteamAPI_ISteamUtils_IsAPICallCompleted(steam_utils, call, &failed);
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "completed", JS_NewBool(js, completed));
  JS_SetPropertyStr(js, obj, "failed", JS_NewBool(js, failed));
  return obj;
)

JSC_CCALL(utils_get_api_call_failure_reason,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  double call_double;
  JS_ToFloat64(js, &call_double, argv[0]);
  SteamAPICall_t call = (SteamAPICall_t)call_double;
  
  ESteamAPICallFailure reason = SteamAPI_ISteamUtils_GetAPICallFailureReason(steam_utils, call);
  return JS_NewUint32(js, reason);
)

JSC_CCALL(utils_get_ipc_call_count,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint32 count = SteamAPI_ISteamUtils_GetIPCCallCount(steam_utils);
  return JS_NewUint32(js, count);
)

JSC_CCALL(utils_overlay_needs_present,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  bool needs_present = SteamAPI_ISteamUtils_BOverlayNeedsPresent(steam_utils);
  return JS_NewBool(js, needs_present);
)

JSC_CCALL(utils_check_file_signature,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  const char *filename = JS_ToCString(js, argv[0]);
  if (!filename) return JS_EXCEPTION;
  
  SteamAPICall_t call = SteamAPI_ISteamUtils_CheckFileSignature(steam_utils, filename);
  JS_FreeCString(js, filename);
  
  return JS_NewFloat64(js, (double)call);
)

JSC_CCALL(utils_show_gamepad_text_input,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint32 input_mode, line_mode, char_max;
  JS_ToUint32(js, &input_mode, argv[0]);
  JS_ToUint32(js, &line_mode, argv[1]);
  
  const char *description = JS_ToCString(js, argv[2]);
  if (!description) return JS_EXCEPTION;
  
  JS_ToUint32(js, &char_max, argv[3]);
  
  const char *existing_text = argc > 4 ? JS_ToCString(js, argv[4]) : "";
  
  bool success = SteamAPI_ISteamUtils_ShowGamepadTextInput(steam_utils, (EGamepadTextInputMode)input_mode, (EGamepadTextInputLineMode)line_mode, description, char_max, existing_text);
  
  JS_FreeCString(js, description);
  if (argc > 4) JS_FreeCString(js, existing_text);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(utils_get_entered_gamepad_text_length,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint32 length = SteamAPI_ISteamUtils_GetEnteredGamepadTextLength(steam_utils);
  return JS_NewUint32(js, length);
)

JSC_CCALL(utils_get_entered_gamepad_text_input,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint32 length = SteamAPI_ISteamUtils_GetEnteredGamepadTextLength(steam_utils);
  if (length == 0) return JS_NewString(js, "");
  
  char *buffer = (char*)js_malloc(js, length + 1);
  if (!buffer) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamUtils_GetEnteredGamepadTextInput(steam_utils, buffer, length + 1);
  
  if (!success) {
    js_free(js, buffer);
    return JS_NewString(js, "");
  }
  
  JSValue result = JS_NewString(js, buffer);
  js_free(js, buffer);
  return result;
)

JSC_CCALL(utils_set_overlay_notification_inset,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  int horizontal_inset, vertical_inset;
  JS_ToInt32(js, &horizontal_inset, argv[0]);
  JS_ToInt32(js, &vertical_inset, argv[1]);
  
  SteamAPI_ISteamUtils_SetOverlayNotificationInset(steam_utils, horizontal_inset, vertical_inset);
  return JS_NULL;
)

JSC_CCALL(utils_start_vr_dashboard,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  SteamAPI_ISteamUtils_StartVRDashboard(steam_utils);
  return JS_NULL;
)

JSC_CCALL(utils_is_vr_headset_streaming_enabled,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  bool enabled = SteamAPI_ISteamUtils_IsVRHeadsetStreamingEnabled(steam_utils);
  return JS_NewBool(js, enabled);
)

JSC_CCALL(utils_set_vr_headset_streaming_enabled,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  bool enabled = JS_ToBool(js, argv[0]);
  SteamAPI_ISteamUtils_SetVRHeadsetStreamingEnabled(steam_utils, enabled);
  return JS_NULL;
)

JSC_CCALL(utils_is_steam_china_launcher,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  bool is_china = SteamAPI_ISteamUtils_IsSteamChinaLauncher(steam_utils);
  return JS_NewBool(js, is_china);
)

JSC_CCALL(utils_init_filter_text,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint32 filter_options;
  JS_ToUint32(js, &filter_options, argv[0]);
  
  bool success = SteamAPI_ISteamUtils_InitFilterText(steam_utils, filter_options);
  return JS_NewBool(js, success);
)

JSC_CCALL(utils_filter_text,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint32 context;
  JS_ToUint32(js, &context, argv[0]);
  
  steamid_t *sid = js2steamid(js, argv[1]);
  if (!sid) return JS_ThrowTypeError(js, "Second argument must be a SteamID");
  
  const char *input_message = JS_ToCString(js, argv[2]);
  if (!input_message) return JS_EXCEPTION;
  
  // Allocate buffer for filtered text (same size as input should be sufficient)
  size_t input_len = strlen(input_message);
  uint32 buffer_size = input_len + 1;
  char *filtered_buffer = (char*)js_malloc(js, buffer_size);
  if (!filtered_buffer) {
    JS_FreeCString(js, input_message);
    return JS_EXCEPTION;
  }
  
  int result = SteamAPI_ISteamUtils_FilterText(steam_utils, (ETextFilteringContext)context, sid->id, input_message, filtered_buffer, buffer_size);
  
  JS_FreeCString(js, input_message);
  
  if (result < 0) {
    js_free(js, filtered_buffer);
    return JS_NULL;
  }
  
  JSValue js_result = JS_NewString(js, filtered_buffer);
  js_free(js, filtered_buffer);
  return js_result;
)

JSC_CCALL(utils_get_ipv6_connectivity_state,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint32 protocol;
  JS_ToUint32(js, &protocol, argv[0]);
  
  ESteamIPv6ConnectivityState state = SteamAPI_ISteamUtils_GetIPv6ConnectivityState(steam_utils, (ESteamIPv6ConnectivityProtocol)protocol);
  return JS_NewUint32(js, state);
)

JSC_CCALL(utils_show_floating_gamepad_text_input,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  uint32 keyboard_mode;
  int x_pos, y_pos, width, height;
  JS_ToUint32(js, &keyboard_mode, argv[0]);
  JS_ToInt32(js, &x_pos, argv[1]);
  JS_ToInt32(js, &y_pos, argv[2]);
  JS_ToInt32(js, &width, argv[3]);
  JS_ToInt32(js, &height, argv[4]);
  
  bool success = SteamAPI_ISteamUtils_ShowFloatingGamepadTextInput(steam_utils, (EFloatingGamepadTextInputMode)keyboard_mode, x_pos, y_pos, width, height);
  return JS_NewBool(js, success);
)

JSC_CCALL(utils_set_game_launcher_mode,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  bool launcher_mode = JS_ToBool(js, argv[0]);
  SteamAPI_ISteamUtils_SetGameLauncherMode(steam_utils, launcher_mode);
  return JS_NULL;
)

JSC_CCALL(utils_dismiss_floating_gamepad_text_input,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  bool success = SteamAPI_ISteamUtils_DismissFloatingGamepadTextInput(steam_utils);
  return JS_NewBool(js, success);
)

JSC_CCALL(utils_dismiss_gamepad_text_input,
  if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized");
  
  bool success = SteamAPI_ISteamUtils_DismissGamepadTextInput(steam_utils);
  return JS_NewBool(js, success);
)

// SCREENSHOTS API
JSC_CCALL(screenshots_write,
  if (!steam_screenshots) return JS_ThrowInternalError(js, "Steam screenshots not initialized");
  
  const char *filename = JS_ToCString(js, argv[0]);
  if (!filename) return JS_EXCEPTION;
  
  uint32 width, height;
  JS_ToUint32(js, &width, argv[1]);
  JS_ToUint32(js, &height, argv[2]);
  
  // Get image data from ArrayBuffer
  size_t data_len;
  uint8_t *data = (uint8_t*)js_get_blob_data(js, &data_len, argv[3]);
  if (!data) {
    JS_FreeCString(js, filename);
    return JS_ThrowTypeError(js, "Fourth argument must be ArrayBuffer containing RGB data");
  }
  
  ScreenshotHandle handle = SteamAPI_ISteamScreenshots_WriteScreenshot(steam_screenshots, data, data_len, width, height);
  JS_FreeCString(js, filename);
  
  return JS_NewUint32(js, handle);
)

JSC_CCALL(screenshots_trigger,
  if (!steam_screenshots) return JS_ThrowInternalError(js, "Steam screenshots not initialized");
  
  SteamAPI_ISteamScreenshots_TriggerScreenshot(steam_screenshots);
  return JS_NULL;
)

// NETWORKING API
JSC_CCALL(networking_send_p2p,
  if (!steam_networking) return JS_ThrowInternalError(js, "Steam networking not initialized");
  
  // Get SteamID
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  // Get data
  size_t data_len;
  uint8_t *data = (uint8_t*)js_get_blob_data(js, &data_len, argv[1]);
  if (!data) return JS_ThrowTypeError(js, "Second argument must be ArrayBuffer");
  
  // Get send type (optional, defaults to reliable)
  EP2PSend send_type = k_EP2PSendReliable;
  if (argc > 2) {
    uint32_t type;
    JS_ToUint32(js, &type, argv[2]);
    send_type = (EP2PSend)type;
  }
  
  // Get channel (optional, defaults to 0)
  int channel = 0;
  if (argc > 3) {
    JS_ToInt32(js, &channel, argv[3]);
  }
  
  bool success = SteamAPI_ISteamNetworking_SendP2PPacket(steam_networking, sid->id, data, data_len, send_type, channel);
  return JS_NewBool(js, success);
)

JSC_CCALL(networking_read_p2p,
  if (!steam_networking) return JS_ThrowInternalError(js, "Steam networking not initialized");
  
  // Get channel (optional, defaults to 0)
  int channel = 0;
  if (argc > 0) {
    JS_ToInt32(js, &channel, argv[0]);
  }
  
  uint32 msg_size;
  if (!SteamAPI_ISteamNetworking_IsP2PPacketAvailable(steam_networking, &msg_size, channel)) {
    return JS_NULL; // No packet available
  }
  
  uint8_t *buffer = (uint8_t*)js_malloc(js, msg_size);
  if (!buffer) return JS_EXCEPTION;
  
  CSteamID remote_steamid;
  uint32 bytes_read;
  bool success = SteamAPI_ISteamNetworking_ReadP2PPacket(steam_networking, buffer, msg_size, &bytes_read, &remote_steamid, channel);
  
  if (!success) {
    js_free(js, buffer);
    return JS_NULL;
  }
  
  JSValue result = JS_NewObject(js);
  JSValue data_buffer = js_new_blob_stoned_copy(js, buffer, bytes_read);
  JSValue steamid_obj = steamid2js(js, remote_steamid.ConvertToUint64());
  
  JS_SetPropertyStr(js, result, "data", data_buffer);
  JS_SetPropertyStr(js, result, "steamid", steamid_obj);
  JS_SetPropertyStr(js, result, "size", JS_NewUint32(js, bytes_read));
  
  js_free(js, buffer);
  return result;
)

JSC_CCALL(networking_accept_p2p,
  if (!steam_networking) return JS_ThrowInternalError(js, "Steam networking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  bool success = SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser(steam_networking, sid->id);
  return JS_NewBool(js, success);
)

JSC_CCALL(networking_close_p2p,
  if (!steam_networking) return JS_ThrowInternalError(js, "Steam networking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  // Get channel (optional, defaults to 0)
  int channel = 0;
  if (argc > 1) {
    JS_ToInt32(js, &channel, argv[1]);
  }
  
  bool success = SteamAPI_ISteamNetworking_CloseP2PSessionWithUser(steam_networking, sid->id);
  return JS_NewBool(js, success);
)

JSC_CCALL(networking_close_p2p_channel,
  if (!steam_networking) return JS_ThrowInternalError(js, "Steam networking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  int channel;
  JS_ToInt32(js, &channel, argv[1]);
  
  bool success = SteamAPI_ISteamNetworking_CloseP2PChannelWithUser(steam_networking, sid->id, channel);
  return JS_NewBool(js, success);
)

JSC_CCALL(networking_p2p_session_state,
  if (!steam_networking) return JS_ThrowInternalError(js, "Steam networking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  P2PSessionState_t state;
  if (!SteamAPI_ISteamNetworking_GetP2PSessionState(steam_networking, sid->id, &state)) {
    return JS_NULL;
  }
  
  JSValue result = JS_NewObject(js);
  JS_SetPropertyStr(js, result, "connection_active", JS_NewBool(js, state.m_bConnectionActive));
  JS_SetPropertyStr(js, result, "connecting", JS_NewBool(js, state.m_bConnecting));
  JS_SetPropertyStr(js, result, "session_error", JS_NewUint32(js, state.m_eP2PSessionError));
  JS_SetPropertyStr(js, result, "using_relay", JS_NewBool(js, state.m_bUsingRelay));
  JS_SetPropertyStr(js, result, "bytes_queued_for_send", JS_NewInt32(js, state.m_nBytesQueuedForSend));
  JS_SetPropertyStr(js, result, "packets_queued_for_send", JS_NewInt32(js, state.m_nPacketsQueuedForSend));
  JS_SetPropertyStr(js, result, "remote_ip", JS_NewUint32(js, state.m_nRemoteIP));
  JS_SetPropertyStr(js, result, "remote_port", JS_NewUint32(js, state.m_nRemotePort));
  
  return result;
)

// MATCHMAKING API
JSC_CCALL(matchmaking_create_lobby,
  if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized");
  
  // Get lobby type (defaults to public)
  ELobbyType lobby_type = k_ELobbyTypePublic;
  if (argc > 0) {
    uint32_t type;
    JS_ToUint32(js, &type, argv[0]);
    lobby_type = (ELobbyType)type;
  }
  
  // Get max members (defaults to 4)
  int max_members = 4;
  if (argc > 1) {
    JS_ToInt32(js, &max_members, argv[1]);
  }
  
  SteamAPICall_t call = SteamAPI_ISteamMatchmaking_CreateLobby(steam_matchmaking, lobby_type, max_members);
  return JS_NewFloat64(js, (double)call);
)

JSC_CCALL(matchmaking_join_lobby,
  if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  SteamAPICall_t call = SteamAPI_ISteamMatchmaking_JoinLobby(steam_matchmaking, sid->id);
  return JS_NewFloat64(js, (double)call);
)

JSC_CCALL(matchmaking_leave_lobby,
  if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID");
  
  SteamAPI_ISteamMatchmaking_LeaveLobby(steam_matchmaking, sid->id);
  return JS_NULL;
)

JSC_CCALL(matchmaking_invite_user,
  if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized");
  
  steamid_t *lobby_sid = js2steamid(js, argv[0]);
  if (!lobby_sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID");
  
  steamid_t *user_sid = js2steamid(js, argv[1]);
  if (!user_sid) return JS_ThrowTypeError(js, "Second argument must be a user SteamID");
  
  bool success = SteamAPI_ISteamMatchmaking_InviteUserToLobby(steam_matchmaking, lobby_sid->id, user_sid->id);
  return JS_NewBool(js, success);
)

JSC_CCALL(matchmaking_lobby_member_count,
  if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID");
  
  int count = SteamAPI_ISteamMatchmaking_GetNumLobbyMembers(steam_matchmaking, sid->id);
  return JS_NewInt32(js, count);
)

JSC_CCALL(matchmaking_lobby_member_by_index,
  if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID");
  
  int index;
  JS_ToInt32(js, &index, argv[1]);
  
  uint64_steamid member = SteamAPI_ISteamMatchmaking_GetLobbyMemberByIndex(steam_matchmaking, sid->id, index);
  return steamid2js(js, member);
)

JSC_CCALL(matchmaking_lobby_owner,
  if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID");
  
  uint64_steamid owner = SteamAPI_ISteamMatchmaking_GetLobbyOwner(steam_matchmaking, sid->id);
  return steamid2js(js, owner);
)

JSC_CCALL(matchmaking_set_lobby_data,
  if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID");
  
  const char *key = JS_ToCString(js, argv[1]);
  if (!key) return JS_EXCEPTION;
  
  const char *value = JS_ToCString(js, argv[2]);
  if (!value) {
    JS_FreeCString(js, key);
    return JS_EXCEPTION;
  }
  
  bool success = SteamAPI_ISteamMatchmaking_SetLobbyData(steam_matchmaking, sid->id, key, value);
  
  JS_FreeCString(js, key);
  JS_FreeCString(js, value);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(matchmaking_get_lobby_data,
  if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID");
  
  const char *key = JS_ToCString(js, argv[1]);
  if (!key) return JS_EXCEPTION;
  
  const char *value = SteamAPI_ISteamMatchmaking_GetLobbyData(steam_matchmaking, sid->id, key);
  JS_FreeCString(js, key);
  
  return JS_NewString(js, value ? value : "");
)

JSC_CCALL(matchmaking_set_lobby_member_data,
  if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID");
  
  const char *key = JS_ToCString(js, argv[1]);
  if (!key) return JS_EXCEPTION;
  
  const char *value = JS_ToCString(js, argv[2]);
  if (!value) {
    JS_FreeCString(js, key);
    return JS_EXCEPTION;
  }
  
  SteamAPI_ISteamMatchmaking_SetLobbyMemberData(steam_matchmaking, sid->id, key, value);
  
  JS_FreeCString(js, key);
  JS_FreeCString(js, value);
  
  return JS_NULL;
)

JSC_CCALL(matchmaking_get_lobby_member_data,
  if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized");
  
  steamid_t *lobby_sid = js2steamid(js, argv[0]);
  if (!lobby_sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID");
  
  steamid_t *user_sid = js2steamid(js, argv[1]);
  if (!user_sid) return JS_ThrowTypeError(js, "Second argument must be a user SteamID");
  
  const char *key = JS_ToCString(js, argv[2]);
  if (!key) return JS_EXCEPTION;
  
  const char *value = SteamAPI_ISteamMatchmaking_GetLobbyMemberData(steam_matchmaking, lobby_sid->id, user_sid->id, key);
  JS_FreeCString(js, key);
  
  return JS_NewString(js, value ? value : "");
)

JSC_CCALL(matchmaking_send_lobby_chat_msg,
  if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized");
  
  steamid_t *sid = js2steamid(js, argv[0]);
  if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID");
  
  size_t msg_len;
  const char *message = JS_ToCStringLen(js, &msg_len, argv[1]);
  if (!message) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamMatchmaking_SendLobbyChatMsg(steam_matchmaking, sid->id, message, msg_len);
  JS_FreeCString(js, message);
  
  return JS_NewBool(js, success);
)

// UGC (User Generated Content) API
JSC_CCALL(ugc_create_query_user_ugc_request,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  uint32 account_id, list_type, matching_type, sort_order, creator_app_id, consumer_app_id, page;
  JS_ToUint32(js, &account_id, argv[0]);
  JS_ToUint32(js, &list_type, argv[1]);
  JS_ToUint32(js, &matching_type, argv[2]);
  JS_ToUint32(js, &sort_order, argv[3]);
  JS_ToUint32(js, &creator_app_id, argv[4]);
  JS_ToUint32(js, &consumer_app_id, argv[5]);
  JS_ToUint32(js, &page, argv[6]);
  
  UGCQueryHandle_t handle = SteamAPI_ISteamUGC_CreateQueryUserUGCRequest(steam_ugc, account_id, (EUserUGCList)list_type, (EUGCMatchingUGCType)matching_type, (EUserUGCListSortOrder)sort_order, creator_app_id, consumer_app_id, page);
  return JS_NewFloat64(js, (double)handle);
)

JSC_CCALL(ugc_create_query_all_ugc_request_page,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  uint32 query_type, matching_type, creator_app_id, consumer_app_id, page;
  JS_ToUint32(js, &query_type, argv[0]);
  JS_ToUint32(js, &matching_type, argv[1]);
  JS_ToUint32(js, &creator_app_id, argv[2]);
  JS_ToUint32(js, &consumer_app_id, argv[3]);
  JS_ToUint32(js, &page, argv[4]);
  
  UGCQueryHandle_t handle = SteamAPI_ISteamUGC_CreateQueryAllUGCRequestPage(steam_ugc, (EUGCQuery)query_type, (EUGCMatchingUGCType)matching_type, creator_app_id, consumer_app_id, page);
  return JS_NewFloat64(js, (double)handle);
)

JSC_CCALL(ugc_create_query_ugc_details_request,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  // Get array of published file IDs
  if (!JS_IsArray(js, argv[0])) return JS_ThrowTypeError(js, "First argument must be an array of published file IDs");
  
  JSValue length_val = JS_GetPropertyStr(js, argv[0], "length");
  uint32 num_ids;
  JS_ToUint32(js, &num_ids, length_val);
  JS_FreeValue(js, length_val);
  
  if (num_ids == 0) return JS_ThrowRangeError(js, "Array must contain at least one published file ID");
  
  PublishedFileId_t *file_ids = (PublishedFileId_t*)js_malloc(js, sizeof(PublishedFileId_t) * num_ids);
  if (!file_ids) return JS_EXCEPTION;
  
  for (uint32 i = 0; i < num_ids; i++) {
    JSValue id_val = JS_GetPropertyUint32(js, argv[0], i);
    double id_double;
    JS_ToFloat64(js, &id_double, id_val);
    file_ids[i] = (PublishedFileId_t)id_double;
    JS_FreeValue(js, id_val);
  }
  
  UGCQueryHandle_t handle = SteamAPI_ISteamUGC_CreateQueryUGCDetailsRequest(steam_ugc, file_ids, num_ids);
  js_free(js, file_ids);
  
  return JS_NewFloat64(js, (double)handle);
)

JSC_CCALL(ugc_send_query_ugc_request,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double handle_double;
  JS_ToFloat64(js, &handle_double, argv[0]);
  UGCQueryHandle_t handle = (UGCQueryHandle_t)handle_double;
  
  SteamAPICall_t call = SteamAPI_ISteamUGC_SendQueryUGCRequest(steam_ugc, handle);
  return JS_NewFloat64(js, (double)call);
)

JSC_CCALL(ugc_release_query_ugc_request,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double handle_double;
  JS_ToFloat64(js, &handle_double, argv[0]);
  UGCQueryHandle_t handle = (UGCQueryHandle_t)handle_double;
  
  bool success = SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(steam_ugc, handle);
  return JS_NewBool(js, success);
)

JSC_CCALL(ugc_add_required_tag,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double handle_double;
  JS_ToFloat64(js, &handle_double, argv[0]);
  UGCQueryHandle_t handle = (UGCQueryHandle_t)handle_double;
  
  const char *tag = JS_ToCString(js, argv[1]);
  if (!tag) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamUGC_AddRequiredTag(steam_ugc, handle, tag);
  JS_FreeCString(js, tag);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(ugc_add_excluded_tag,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double handle_double;
  JS_ToFloat64(js, &handle_double, argv[0]);
  UGCQueryHandle_t handle = (UGCQueryHandle_t)handle_double;
  
  const char *tag = JS_ToCString(js, argv[1]);
  if (!tag) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamUGC_AddExcludedTag(steam_ugc, handle, tag);
  JS_FreeCString(js, tag);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(ugc_set_search_text,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double handle_double;
  JS_ToFloat64(js, &handle_double, argv[0]);
  UGCQueryHandle_t handle = (UGCQueryHandle_t)handle_double;
  
  const char *search_text = JS_ToCString(js, argv[1]);
  if (!search_text) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamUGC_SetSearchText(steam_ugc, handle, search_text);
  JS_FreeCString(js, search_text);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(ugc_set_language,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double handle_double;
  JS_ToFloat64(js, &handle_double, argv[0]);
  UGCQueryHandle_t handle = (UGCQueryHandle_t)handle_double;
  
  const char *language = JS_ToCString(js, argv[1]);
  if (!language) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamUGC_SetLanguage(steam_ugc, handle, language);
  JS_FreeCString(js, language);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(ugc_subscribe_item,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double file_id;
  JS_ToFloat64(js, &file_id, argv[0]);
  
  SteamAPICall_t call = SteamAPI_ISteamUGC_SubscribeItem(steam_ugc, (PublishedFileId_t)file_id);
  return JS_NewFloat64(js, (double)call);
)

JSC_CCALL(ugc_unsubscribe_item,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double file_id;
  JS_ToFloat64(js, &file_id, argv[0]);
  
  SteamAPICall_t call = SteamAPI_ISteamUGC_UnsubscribeItem(steam_ugc, (PublishedFileId_t)file_id);
  return JS_NewFloat64(js, (double)call);
)

JSC_CCALL(ugc_get_num_subscribed_items,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  bool include_locally_disabled = argc > 0 ? JS_ToBool(js, argv[0]) : false;
  
  uint32 count = SteamAPI_ISteamUGC_GetNumSubscribedItems(steam_ugc, include_locally_disabled);
  return JS_NewUint32(js, count);
)

JSC_CCALL(ugc_get_subscribed_items,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  uint32 max_entries;
  JS_ToUint32(js, &max_entries, argv[0]);
  
  bool include_locally_disabled = argc > 1 ? JS_ToBool(js, argv[1]) : false;
  
  if (max_entries == 0) return JS_NewArray(js);
  
  PublishedFileId_t *file_ids = (PublishedFileId_t*)js_malloc(js, sizeof(PublishedFileId_t) * max_entries);
  if (!file_ids) return JS_EXCEPTION;
  
  uint32 actual_count = SteamAPI_ISteamUGC_GetSubscribedItems(steam_ugc, file_ids, max_entries, include_locally_disabled);
  
  JSValue result = JS_NewArray(js);
  for (uint32 i = 0; i < actual_count; i++) {
    JS_SetPropertyUint32(js, result, i, JS_NewFloat64(js, (double)file_ids[i]));
  }
  
  js_free(js, file_ids);
  return result;
)

JSC_CCALL(ugc_get_item_state,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double file_id;
  JS_ToFloat64(js, &file_id, argv[0]);
  
  uint32 state = SteamAPI_ISteamUGC_GetItemState(steam_ugc, (PublishedFileId_t)file_id);
  return JS_NewUint32(js, state);
)

JSC_CCALL(ugc_get_item_install_info,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double file_id;
  JS_ToFloat64(js, &file_id, argv[0]);
  
  uint64 size_on_disk;
  char folder[1024];
  uint32 timestamp;
  
  bool success = SteamAPI_ISteamUGC_GetItemInstallInfo(steam_ugc, (PublishedFileId_t)file_id, &size_on_disk, folder, sizeof(folder), &timestamp);
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "size_on_disk", JS_NewFloat64(js, (double)size_on_disk));
  JS_SetPropertyStr(js, obj, "folder", JS_NewString(js, folder));
  JS_SetPropertyStr(js, obj, "timestamp", JS_NewUint32(js, timestamp));
  return obj;
)

JSC_CCALL(ugc_get_item_download_info,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double file_id;
  JS_ToFloat64(js, &file_id, argv[0]);
  
  uint64 bytes_downloaded, bytes_total;
  
  bool success = SteamAPI_ISteamUGC_GetItemDownloadInfo(steam_ugc, (PublishedFileId_t)file_id, &bytes_downloaded, &bytes_total);
  
  if (!success) return JS_NULL;
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "bytes_downloaded", JS_NewFloat64(js, (double)bytes_downloaded));
  JS_SetPropertyStr(js, obj, "bytes_total", JS_NewFloat64(js, (double)bytes_total));
  return obj;
)

JSC_CCALL(ugc_download_item,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double file_id;
  JS_ToFloat64(js, &file_id, argv[0]);
  
  bool high_priority = argc > 1 ? JS_ToBool(js, argv[1]) : false;
  
  bool success = SteamAPI_ISteamUGC_DownloadItem(steam_ugc, (PublishedFileId_t)file_id, high_priority);
  return JS_NewBool(js, success);
)

JSC_CCALL(ugc_create_item,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  uint32 consumer_app_id, file_type;
  JS_ToUint32(js, &consumer_app_id, argv[0]);
  JS_ToUint32(js, &file_type, argv[1]);
  
  SteamAPICall_t call = SteamAPI_ISteamUGC_CreateItem(steam_ugc, consumer_app_id, (EWorkshopFileType)file_type);
  return JS_NewFloat64(js, (double)call);
)

JSC_CCALL(ugc_start_item_update,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  uint32 consumer_app_id;
  double file_id;
  JS_ToUint32(js, &consumer_app_id, argv[0]);
  JS_ToFloat64(js, &file_id, argv[1]);
  
  UGCUpdateHandle_t handle = SteamAPI_ISteamUGC_StartItemUpdate(steam_ugc, consumer_app_id, (PublishedFileId_t)file_id);
  return JS_NewFloat64(js, (double)handle);
)

JSC_CCALL(ugc_set_item_title,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double handle_double;
  JS_ToFloat64(js, &handle_double, argv[0]);
  UGCUpdateHandle_t handle = (UGCUpdateHandle_t)handle_double;
  
  const char *title = JS_ToCString(js, argv[1]);
  if (!title) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamUGC_SetItemTitle(steam_ugc, handle, title);
  JS_FreeCString(js, title);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(ugc_set_item_description,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double handle_double;
  JS_ToFloat64(js, &handle_double, argv[0]);
  UGCUpdateHandle_t handle = (UGCUpdateHandle_t)handle_double;
  
  const char *description = JS_ToCString(js, argv[1]);
  if (!description) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamUGC_SetItemDescription(steam_ugc, handle, description);
  JS_FreeCString(js, description);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(ugc_set_item_content,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double handle_double;
  JS_ToFloat64(js, &handle_double, argv[0]);
  UGCUpdateHandle_t handle = (UGCUpdateHandle_t)handle_double;
  
  const char *content_folder = JS_ToCString(js, argv[1]);
  if (!content_folder) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamUGC_SetItemContent(steam_ugc, handle, content_folder);
  JS_FreeCString(js, content_folder);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(ugc_set_item_preview,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double handle_double;
  JS_ToFloat64(js, &handle_double, argv[0]);
  UGCUpdateHandle_t handle = (UGCUpdateHandle_t)handle_double;
  
  const char *preview_file = JS_ToCString(js, argv[1]);
  if (!preview_file) return JS_EXCEPTION;
  
  bool success = SteamAPI_ISteamUGC_SetItemPreview(steam_ugc, handle, preview_file);
  JS_FreeCString(js, preview_file);
  
  return JS_NewBool(js, success);
)

JSC_CCALL(ugc_submit_item_update,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double handle_double;
  JS_ToFloat64(js, &handle_double, argv[0]);
  UGCUpdateHandle_t handle = (UGCUpdateHandle_t)handle_double;
  
  const char *change_note = argc > 1 ? JS_ToCString(js, argv[1]) : NULL;
  
  SteamAPICall_t call = SteamAPI_ISteamUGC_SubmitItemUpdate(steam_ugc, handle, change_note);
  
  if (change_note) JS_FreeCString(js, change_note);
  
  return JS_NewFloat64(js, (double)call);
)

JSC_CCALL(ugc_get_item_update_progress,
  if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized");
  
  double handle_double;
  JS_ToFloat64(js, &handle_double, argv[0]);
  UGCUpdateHandle_t handle = (UGCUpdateHandle_t)handle_double;
  
  uint64 bytes_processed, bytes_total;
  
  EItemUpdateStatus status = SteamAPI_ISteamUGC_GetItemUpdateProgress(steam_ugc, handle, &bytes_processed, &bytes_total);
  
  JSValue obj = JS_NewObject(js);
  JS_SetPropertyStr(js, obj, "status", JS_NewUint32(js, status));
  JS_SetPropertyStr(js, obj, "bytes_processed", JS_NewFloat64(js, (double)bytes_processed));
  JS_SetPropertyStr(js, obj, "bytes_total", JS_NewFloat64(js, (double)bytes_total));
  return obj;
)

// Function lists for sub-objects
static const JSCFunctionListEntry js_steam_stats_funcs[] = {
  MIST_FUNC_DEF(stats, request, 0),
  MIST_FUNC_DEF(stats, store, 0),
  MIST_FUNC_DEF(stats, get_int, 1),
  MIST_FUNC_DEF(stats, get_float, 1),
  MIST_FUNC_DEF(stats, set_int, 2),
  MIST_FUNC_DEF(stats, set_float, 2),
  MIST_FUNC_DEF(stats, update_avg_rate, 3),
  MIST_FUNC_DEF(stats, get_user_stat_int, 2),
  MIST_FUNC_DEF(stats, get_user_stat_float, 2),
  MIST_FUNC_DEF(stats, reset_all, 1),
  MIST_FUNC_DEF(stats, get_number_of_current_players, 0),
  MIST_FUNC_DEF(stats, request_global_achievement_percentages, 0),
  MIST_FUNC_DEF(stats, request_global, 1),
  MIST_FUNC_DEF(stats, get_global_stat_int64, 1),
  MIST_FUNC_DEF(stats, get_global_stat_double, 1),
  MIST_FUNC_DEF(stats, get_global_stat_history_int64, 1),
  MIST_FUNC_DEF(stats, get_global_stat_history_double, 1),
};

static const JSCFunctionListEntry js_steam_achievement_funcs[] = {
  MIST_FUNC_DEF(achievement, get, 1),
  MIST_FUNC_DEF(achievement, set, 1),
  MIST_FUNC_DEF(achievement, clear, 1),
  MIST_FUNC_DEF(achievement, count, 0),
  MIST_FUNC_DEF(achievement, name, 1),
  MIST_FUNC_DEF(achievement, get_and_unlock_time, 1),
  MIST_FUNC_DEF(achievement, get_icon, 1),
  MIST_FUNC_DEF(achievement, get_display_attribute, 2),
  MIST_FUNC_DEF(achievement, indicate_progress, 3),
  MIST_FUNC_DEF(achievement, get_user_achievement, 2),
  MIST_FUNC_DEF(achievement, get_user_achievement_and_unlock_time, 2),
  MIST_FUNC_DEF(achievement, get_achieved_percent, 1),
  MIST_FUNC_DEF(achievement, get_progress_limits_int, 1),
  MIST_FUNC_DEF(achievement, get_progress_limits_float, 1),
  MIST_FUNC_DEF(achievement, get_most_achieved_info, 0),
  MIST_FUNC_DEF(achievement, get_next_most_achieved_info, 1),
};

static const JSCFunctionListEntry js_steam_leaderboard_funcs[] = {
  MIST_FUNC_DEF(leaderboard, find_or_create, 3),
  MIST_FUNC_DEF(leaderboard, find, 1),
  MIST_FUNC_DEF(leaderboard, get_name, 1),
  MIST_FUNC_DEF(leaderboard, get_entry_count, 1),
  MIST_FUNC_DEF(leaderboard, get_sort_method, 1),
  MIST_FUNC_DEF(leaderboard, get_display_type, 1),
  MIST_FUNC_DEF(leaderboard, download_entries, 4),
  MIST_FUNC_DEF(leaderboard, download_entries_for_users, 2),
  MIST_FUNC_DEF(leaderboard, get_downloaded_entry, 2),
  MIST_FUNC_DEF(leaderboard, upload_score, 3),
  MIST_FUNC_DEF(leaderboard, attach_ugc, 2),
};

static const JSCFunctionListEntry js_steam_app_funcs[] = {
  MIST_FUNC_DEF(app, id, 0),
  MIST_FUNC_DEF(app, owner, 0),
  MIST_FUNC_DEF(app, installed, 1),
  MIST_FUNC_DEF(app, subscribed, 0),
  MIST_FUNC_DEF(app, language, 0),
  MIST_FUNC_DEF(app, dlc_installed, 1),
  MIST_FUNC_DEF(app, is_low_violence, 0),
  MIST_FUNC_DEF(app, is_cybercafe, 0),
  MIST_FUNC_DEF(app, is_vac_banned, 0),
  MIST_FUNC_DEF(app, available_languages, 0),
  MIST_FUNC_DEF(app, is_subscribed_app, 1),
  MIST_FUNC_DEF(app, earliest_purchase_time, 1),
  MIST_FUNC_DEF(app, is_subscribed_from_free_weekend, 0),
  MIST_FUNC_DEF(app, dlc_count, 0),
  MIST_FUNC_DEF(app, get_dlc_data_by_index, 1),
  MIST_FUNC_DEF(app, install_dlc, 1),
  MIST_FUNC_DEF(app, uninstall_dlc, 1),
  MIST_FUNC_DEF(app, request_proof_of_purchase_key, 1),
  MIST_FUNC_DEF(app, get_current_beta_name, 0),
  MIST_FUNC_DEF(app, mark_content_corrupt, 1),
  MIST_FUNC_DEF(app, get_installed_depots, 1),
  MIST_FUNC_DEF(app, get_install_dir, 1),
  MIST_FUNC_DEF(app, get_launch_query_param, 1),
  MIST_FUNC_DEF(app, get_dlc_download_progress, 1),
  MIST_FUNC_DEF(app, get_build_id, 0),
  MIST_FUNC_DEF(app, request_all_proof_of_purchase_keys, 0),
  MIST_FUNC_DEF(app, get_file_details, 1),
  MIST_FUNC_DEF(app, get_launch_command_line, 0),
  MIST_FUNC_DEF(app, is_subscribed_from_family_sharing, 0),
  MIST_FUNC_DEF(app, is_timed_trial, 0),
  MIST_FUNC_DEF(app, set_dlc_context, 1),
  MIST_FUNC_DEF(app, get_num_betas, 0),
  MIST_FUNC_DEF(app, get_beta_info, 1),
  MIST_FUNC_DEF(app, set_active_beta, 1),
};

static const JSCFunctionListEntry js_steam_user_funcs[] = {
  MIST_FUNC_DEF(user, logged_on, 0),
  MIST_FUNC_DEF(user, steam_id, 0),
  MIST_FUNC_DEF(user, level, 0),
  MIST_FUNC_DEF(user, hsteam_user, 0),
  MIST_FUNC_DEF(user, get_user_data_folder, 0),
  MIST_FUNC_DEF(user, start_voice_recording, 0),
  MIST_FUNC_DEF(user, stop_voice_recording, 0),
  MIST_FUNC_DEF(user, get_available_voice, 1),
  MIST_FUNC_DEF(user, get_voice, 1),
  MIST_FUNC_DEF(user, decompress_voice, 3),
  MIST_FUNC_DEF(user, get_voice_optimal_sample_rate, 0),
  MIST_FUNC_DEF(user, get_auth_session_ticket, 0),
  MIST_FUNC_DEF(user, get_auth_ticket_for_web_api, 1),
  MIST_FUNC_DEF(user, begin_auth_session, 2),
  MIST_FUNC_DEF(user, end_auth_session, 1),
  MIST_FUNC_DEF(user, cancel_auth_ticket, 1),
  MIST_FUNC_DEF(user, has_license_for_app, 2),
  MIST_FUNC_DEF(user, is_behind_nat, 0),
  MIST_FUNC_DEF(user, advertise_game, 3),
  MIST_FUNC_DEF(user, request_encrypted_app_ticket, 1),
  MIST_FUNC_DEF(user, get_encrypted_app_ticket, 0),
  MIST_FUNC_DEF(user, get_game_badge_level, 2),
  MIST_FUNC_DEF(user, request_store_auth_url, 1),
  MIST_FUNC_DEF(user, is_phone_verified, 0),
  MIST_FUNC_DEF(user, is_two_factor_enabled, 0),
  MIST_FUNC_DEF(user, is_phone_identifying, 0),
  MIST_FUNC_DEF(user, is_phone_requiring_verification, 0),
  MIST_FUNC_DEF(user, get_market_eligibility, 0),
  MIST_FUNC_DEF(user, get_duration_control, 0),
  MIST_FUNC_DEF(user, set_duration_control_online_state, 1),
  MIST_FUNC_DEF(user, track_app_usage_event, 3),
  MIST_FUNC_DEF(user, initiate_game_connection_deprecated, 4),
  MIST_FUNC_DEF(user, terminate_game_connection_deprecated, 2),
};

static const JSCFunctionListEntry js_steam_friends_funcs[] = {
  MIST_FUNC_DEF(friends, name, 0),
  MIST_FUNC_DEF(friends, state, 0),
  MIST_FUNC_DEF(friends, count, 1),
  MIST_FUNC_DEF(friends, get_friend, 2),
  MIST_FUNC_DEF(friends, get_friend_name, 1),
  MIST_FUNC_DEF(friends, activate_overlay, 1),
  MIST_FUNC_DEF(friends, activate_overlay_to_user, 2),
  MIST_FUNC_DEF(friends, activate_overlay_to_web, 1),
  MIST_FUNC_DEF(friends, set_rich_presence, 2),
  MIST_FUNC_DEF(friends, clear_rich_presence, 0),
};

static const JSCFunctionListEntry js_steam_cloud_funcs[] = {
  MIST_FUNC_DEF(cloud, enabled_app, 0),
  MIST_FUNC_DEF(cloud, enabled_account, 0),
  MIST_FUNC_DEF(cloud, enable, 1),
  MIST_FUNC_DEF(cloud, quota, 0),
  MIST_FUNC_DEF(cloud, write, 2),
  MIST_FUNC_DEF(cloud, read, 1),
  MIST_FUNC_DEF(cloud, delete, 1),
  MIST_FUNC_DEF(cloud, exists, 1),
};

static const JSCFunctionListEntry js_steam_utils_funcs[] = {
  MIST_FUNC_DEF(utils, overlay_enabled, 0),
  MIST_FUNC_DEF(utils, big_picture_mode, 0),
  MIST_FUNC_DEF(utils, vr_mode, 0),
  MIST_FUNC_DEF(utils, steam_deck, 0),
  MIST_FUNC_DEF(utils, battery_power, 0),
  MIST_FUNC_DEF(utils, seconds_since_app_active, 0),
  MIST_FUNC_DEF(utils, ip_country, 0),
  MIST_FUNC_DEF(utils, ui_language, 0),
  MIST_FUNC_DEF(utils, get_seconds_since_computer_active, 0),
  MIST_FUNC_DEF(utils, get_connected_universe, 0),
  MIST_FUNC_DEF(utils, get_server_real_time, 0),
  MIST_FUNC_DEF(utils, get_image_size, 1),
  MIST_FUNC_DEF(utils, get_image_rgba, 1),
  MIST_FUNC_DEF(utils, set_overlay_notification_position, 1),
  MIST_FUNC_DEF(utils, is_api_call_completed, 1),
  MIST_FUNC_DEF(utils, get_api_call_failure_reason, 1),
  MIST_FUNC_DEF(utils, get_ipc_call_count, 0),
  MIST_FUNC_DEF(utils, overlay_needs_present, 0),
  MIST_FUNC_DEF(utils, check_file_signature, 1),
  MIST_FUNC_DEF(utils, show_gamepad_text_input, 5),
  MIST_FUNC_DEF(utils, get_entered_gamepad_text_length, 0),
  MIST_FUNC_DEF(utils, get_entered_gamepad_text_input, 0),
  MIST_FUNC_DEF(utils, set_overlay_notification_inset, 2),
  MIST_FUNC_DEF(utils, start_vr_dashboard, 0),
  MIST_FUNC_DEF(utils, is_vr_headset_streaming_enabled, 0),
  MIST_FUNC_DEF(utils, set_vr_headset_streaming_enabled, 1),
  MIST_FUNC_DEF(utils, is_steam_china_launcher, 0),
  MIST_FUNC_DEF(utils, init_filter_text, 1),
  MIST_FUNC_DEF(utils, filter_text, 3),
  MIST_FUNC_DEF(utils, get_ipv6_connectivity_state, 1),
  MIST_FUNC_DEF(utils, show_floating_gamepad_text_input, 5),
  MIST_FUNC_DEF(utils, set_game_launcher_mode, 1),
  MIST_FUNC_DEF(utils, dismiss_floating_gamepad_text_input, 0),
  MIST_FUNC_DEF(utils, dismiss_gamepad_text_input, 0),
};

static const JSCFunctionListEntry js_steam_screenshots_funcs[] = {
  MIST_FUNC_DEF(screenshots, write, 4),
  MIST_FUNC_DEF(screenshots, trigger, 0),
};

static const JSCFunctionListEntry js_steam_networking_funcs[] = {
  MIST_FUNC_DEF(networking, send_p2p, 4),
  MIST_FUNC_DEF(networking, read_p2p, 1),
  MIST_FUNC_DEF(networking, accept_p2p, 1),
  MIST_FUNC_DEF(networking, close_p2p, 2),
  MIST_FUNC_DEF(networking, close_p2p_channel, 2),
  MIST_FUNC_DEF(networking, p2p_session_state, 1),
};

static const JSCFunctionListEntry js_steam_matchmaking_funcs[] = {
  MIST_FUNC_DEF(matchmaking, create_lobby, 2),
  MIST_FUNC_DEF(matchmaking, join_lobby, 1),
  MIST_FUNC_DEF(matchmaking, leave_lobby, 1),
  MIST_FUNC_DEF(matchmaking, invite_user, 2),
  MIST_FUNC_DEF(matchmaking, lobby_member_count, 1),
  MIST_FUNC_DEF(matchmaking, lobby_member_by_index, 2),
  MIST_FUNC_DEF(matchmaking, lobby_owner, 1),
  MIST_FUNC_DEF(matchmaking, set_lobby_data, 3),
  MIST_FUNC_DEF(matchmaking, get_lobby_data, 2),
  MIST_FUNC_DEF(matchmaking, set_lobby_member_data, 3),
  MIST_FUNC_DEF(matchmaking, get_lobby_member_data, 3),
  MIST_FUNC_DEF(matchmaking, send_lobby_chat_msg, 2),
};

static const JSCFunctionListEntry js_steam_ugc_funcs[] = {
  MIST_FUNC_DEF(ugc, create_query_user_ugc_request, 7),
  MIST_FUNC_DEF(ugc, create_query_all_ugc_request_page, 5),
  MIST_FUNC_DEF(ugc, create_query_ugc_details_request, 1),
  MIST_FUNC_DEF(ugc, send_query_ugc_request, 1),
  MIST_FUNC_DEF(ugc, release_query_ugc_request, 1),
  MIST_FUNC_DEF(ugc, add_required_tag, 2),
  MIST_FUNC_DEF(ugc, add_excluded_tag, 2),
  MIST_FUNC_DEF(ugc, set_search_text, 2),
  MIST_FUNC_DEF(ugc, set_language, 2),
  MIST_FUNC_DEF(ugc, subscribe_item, 1),
  MIST_FUNC_DEF(ugc, unsubscribe_item, 1),
  MIST_FUNC_DEF(ugc, get_num_subscribed_items, 1),
  MIST_FUNC_DEF(ugc, get_subscribed_items, 2),
  MIST_FUNC_DEF(ugc, get_item_state, 1),
  MIST_FUNC_DEF(ugc, get_item_install_info, 1),
  MIST_FUNC_DEF(ugc, get_item_download_info, 1),
  MIST_FUNC_DEF(ugc, download_item, 2),
  MIST_FUNC_DEF(ugc, create_item, 2),
  MIST_FUNC_DEF(ugc, start_item_update, 2),
  MIST_FUNC_DEF(ugc, set_item_title, 2),
  MIST_FUNC_DEF(ugc, set_item_description, 2),
  MIST_FUNC_DEF(ugc, set_item_content, 2),
  MIST_FUNC_DEF(ugc, set_item_preview, 2),
  MIST_FUNC_DEF(ugc, submit_item_update, 2),
  MIST_FUNC_DEF(ugc, get_item_update_progress, 1),
};

// Main Steam API functions
static const JSCFunctionListEntry js_steam_funcs[] = {
  MIST_FUNC_DEF(steam, init, 0),
  MIST_FUNC_DEF(steam, shutdown, 0),
  MIST_FUNC_DEF(steam, run_callbacks, 0),
};

JSValue js_steam_use(JSContext *js) {
  // Register SteamID class if not already done
  static int steamid_class_initialized = 0;
  if (!steamid_class_initialized) {
    JS_NewClassID(&js_steamid_id);
    JS_NewClass(JS_GetRuntime(js), js_steamid_id, &js_steamid_class);
    
    JSValue steamid_proto = JS_NewObject(js);
    JS_SetPropertyFunctionList(js, steamid_proto, js_steamid_proto_funcs, countof(js_steamid_proto_funcs));
    JS_SetClassProto(js, js_steamid_id, steamid_proto);
    
    steamid_class_initialized = 1;
  }
  
  JSValue steam = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, steam, js_steam_funcs, countof(js_steam_funcs));
  
  // Stats sub-object
  JSValue stats = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, stats, js_steam_stats_funcs, countof(js_steam_stats_funcs));
  JS_SetPropertyStr(js, steam, "stats", stats);
  
  // Achievement sub-object
  JSValue achievement = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, achievement, js_steam_achievement_funcs, countof(js_steam_achievement_funcs));
  JS_SetPropertyStr(js, steam, "achievement", achievement);
  
  // Leaderboard sub-object
  JSValue leaderboard = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, leaderboard, js_steam_leaderboard_funcs, countof(js_steam_leaderboard_funcs));
  JS_SetPropertyStr(js, steam, "leaderboard", leaderboard);
  
  // App sub-object
  JSValue app = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, app, js_steam_app_funcs, countof(js_steam_app_funcs));
  JS_SetPropertyStr(js, steam, "app", app);
  
  // User sub-object
  JSValue user = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, user, js_steam_user_funcs, countof(js_steam_user_funcs));
  JS_SetPropertyStr(js, steam, "user", user);
  
  // Friends sub-object
  JSValue friends = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, friends, js_steam_friends_funcs, countof(js_steam_friends_funcs));
  JS_SetPropertyStr(js, steam, "friends", friends);
  
  // Cloud sub-object
  JSValue cloud = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, cloud, js_steam_cloud_funcs, countof(js_steam_cloud_funcs));
  JS_SetPropertyStr(js, steam, "cloud", cloud);
  
  // Utils sub-object
  JSValue utils = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, utils, js_steam_utils_funcs, countof(js_steam_utils_funcs));
  JS_SetPropertyStr(js, steam, "utils", utils);
  
  // Screenshots sub-object
  JSValue screenshots = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, screenshots, js_steam_screenshots_funcs, countof(js_steam_screenshots_funcs));
  JS_SetPropertyStr(js, steam, "screenshots", screenshots);
  
  // Networking sub-object
  JSValue networking = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, networking, js_steam_networking_funcs, countof(js_steam_networking_funcs));
  JS_SetPropertyStr(js, steam, "networking", networking);
  
  // Matchmaking sub-object
  JSValue matchmaking = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, matchmaking, js_steam_matchmaking_funcs, countof(js_steam_matchmaking_funcs));
  JS_SetPropertyStr(js, steam, "matchmaking", matchmaking);
  
  // UGC sub-object
  JSValue ugc = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, ugc, js_steam_ugc_funcs, countof(js_steam_ugc_funcs));
  JS_SetPropertyStr(js, steam, "ugc", ugc);
  
  return steam;
}

} // extern "C"

#else
// Stub when Steam is disabled
extern "C" JSValue js_steam_use(JSContext *js) {
  return JS_NULL;
}
#endif
