#include <SDL3/SDL_stdinc.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_sdl3.h"
#include "helper.h"
#include "sdl3_image.h"
#include "sdl3_ttf.h"
#include "sdl3_events.h"
#include <SDL3/SDL.h>
#include <math.h>
#include <string.h>

#ifdef HAVE_LIBNOTIFY
#include <libnotify/notify.h>
#endif

// SDL3 native tray support
#include <SDL3/SDL_tray.h>

// Resource handles (nicht static, damit sie in anderen Modulen verfügbar sind)
int le_sdl_window;
int le_sdl_renderer;
int le_sdl_texture;

// Destructor for window resource
static void sdl_window_dtor(zend_resource *rsrc) {
    SDL_Window *win = (SDL_Window *)rsrc->ptr;
    if (win) {
        SDL_DestroyWindow(win);
    }
}

// Destructor for renderer resource
static void sdl_renderer_dtor(zend_resource *rsrc) {
    SDL_Renderer *ren = (SDL_Renderer *)rsrc->ptr;
    if (ren) {
        SDL_DestroyRenderer(ren);
    }
}

// Destructor for texture resource
static void sdl_texture_dtor(zend_resource *rsrc) {
    SDL_Texture *tex = (SDL_Texture *)rsrc->ptr;
    if (tex) {
        SDL_DestroyTexture(tex);
    }
}

// --- Tray integration state ---
// SDL3 Tray globals
static SDL_Tray *g_sdl_tray = NULL;
static SDL_TrayMenu *g_sdl_tray_menu = NULL;
static SDL_TrayEntry **g_sdl_tray_entries = NULL;
static int g_sdl_tray_entry_count = 0;
static zval *g_tray_callbacks = NULL;  // Array of PHP callbacks for each tray entry

static void SDLCALL php_tray_callback(void *userdata, SDL_TrayEntry *entry) {
    intptr_t idx = (intptr_t)userdata;
    idx = idx-1;
    // Log all callback invocations for debugging
    php_error_docref(NULL, E_NOTICE, "Tray callback invoked: userdata=%p (idx=%d), g_tray_callbacks=%p",
                     userdata, (int)idx, (void*)g_tray_callbacks);

    // userdata can be NULL for events without callbacks (e.g., clicking the tray icon itself)
    // This is normal, so just return silently
    if (!userdata || !g_tray_callbacks) {
        php_error_docref(NULL, E_NOTICE, "Tray callback: skipping (userdata=%p, callbacks=%p)",
                         userdata, (void*)g_tray_callbacks);
        return;
    }

    // Check if we have a callback for this index
    if (idx < 0 || idx >= g_sdl_tray_entry_count) {
        php_error_docref(NULL, E_WARNING, "Tray callback: invalid index %d (max %d)", (int)idx, g_sdl_tray_entry_count);
        return;
    }

    zval *callback = &g_tray_callbacks[idx];

    // Only call if callback is set and callable
    if (Z_TYPE_P(callback) == IS_UNDEF) {
        php_error_docref(NULL, E_WARNING, "Tray callback %d: callback is undefined", (int)idx);
        return;
    }

    if (!zend_is_callable(callback, 0, NULL)) {
        php_error_docref(NULL, E_WARNING, "Tray callback %d: callback is not callable", (int)idx);
        return;
    }

    zval retval;
    zval params[1];

    // Pass the index as parameter to the callback
    ZVAL_LONG(&params[0], idx);

    // Call the PHP callback
    int result = call_user_function(EG(function_table), NULL, callback, &retval, 1, params);

    if (result == SUCCESS) {
        zval_ptr_dtor(&retval);
    } else {
        php_error_docref(NULL, E_WARNING, "Tray callback %d: call_user_function failed with code %d", (int)idx, result);
    }
}

PHP_MINIT_FUNCTION(sdl3) {
    le_sdl_window = zend_register_list_destructors_ex(sdl_window_dtor, NULL, "SDL_Window", module_number);
    le_sdl_renderer = zend_register_list_destructors_ex(sdl_renderer_dtor, NULL, "SDL_Renderer", module_number);
    le_sdl_texture = zend_register_list_destructors_ex(sdl_texture_dtor, NULL, "SDL_Texture", module_number);

    // Registriere SDL3_image und SDL3_ttf Resources
    sdl3_image_register_resources(module_number);
    sdl3_ttf_register_resources(module_number);

    // Registriere Event-Konstanten
    sdl3_events_register_constants(module_number);

    // SDL Init Flags
    REGISTER_LONG_CONSTANT("SDL_INIT_VIDEO", SDL_INIT_VIDEO, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_INIT_AUDIO", SDL_INIT_AUDIO, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_INIT_EVENTS", SDL_INIT_EVENTS, CONST_CS | CONST_PERSISTENT);

    // SDL Window Flags
    REGISTER_LONG_CONSTANT("SDL_WINDOW_FULLSCREEN", SDL_WINDOW_FULLSCREEN, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_WINDOW_OPENGL", SDL_WINDOW_OPENGL, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_WINDOW_HIDDEN", SDL_WINDOW_HIDDEN, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_WINDOW_BORDERLESS", SDL_WINDOW_BORDERLESS, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_WINDOW_RESIZABLE", SDL_WINDOW_RESIZABLE, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_WINDOW_MINIMIZED", SDL_WINDOW_MINIMIZED, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_WINDOW_MAXIMIZED", SDL_WINDOW_MAXIMIZED, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_WINDOW_HIGH_PIXEL_DENSITY", SDL_WINDOW_HIGH_PIXEL_DENSITY, CONST_CS | CONST_PERSISTENT);

    // SDL Pixel Formats
    REGISTER_LONG_CONSTANT("SDL_PIXELFORMAT_RGBA8888", SDL_PIXELFORMAT_RGBA8888, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_PIXELFORMAT_ARGB8888", SDL_PIXELFORMAT_ARGB8888, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_PIXELFORMAT_BGRA8888", SDL_PIXELFORMAT_BGRA8888, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_PIXELFORMAT_ABGR8888", SDL_PIXELFORMAT_ABGR8888, CONST_CS | CONST_PERSISTENT);

    // SDL Texture Access
    REGISTER_LONG_CONSTANT("SDL_TEXTUREACCESS_STATIC", SDL_TEXTUREACCESS_STATIC, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_TEXTUREACCESS_STREAMING", SDL_TEXTUREACCESS_STREAMING, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_TEXTUREACCESS_TARGET", SDL_TEXTUREACCESS_TARGET, CONST_CS | CONST_PERSISTENT);

    // SDL Blend Modes
    REGISTER_LONG_CONSTANT("SDL_BLENDMODE_NONE", SDL_BLENDMODE_NONE, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_BLENDMODE_BLEND", SDL_BLENDMODE_BLEND, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_BLENDMODE_ADD", SDL_BLENDMODE_ADD, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_BLENDMODE_MOD", SDL_BLENDMODE_MOD, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("SDL_BLENDMODE_MUL", SDL_BLENDMODE_MUL, CONST_CS | CONST_PERSISTENT);

    return SUCCESS;
}

PHP_FUNCTION(sdl_init) {
    zend_long flags;
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &flags) == FAILURE) {
        RETURN_THROWS();
    }
    if (SDL_Init((Uint32)flags) < 0) {
        RETURN_FALSE;
    }
    RETURN_TRUE;
}

PHP_FUNCTION(sdl_quit) {
    SDL_Quit();
}

PHP_FUNCTION(sdl_create_window) {
    char *title;
    size_t title_len;
    zend_long w, h, flags = 0;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "sll|l", &title, &title_len, &w, &h, &flags) == FAILURE) {
        RETURN_THROWS();
    }

    SDL_Window *win = SDL_CreateWindow(title, (int)w, (int)h, (SDL_WindowFlags)flags);
    if (!win) {
        RETURN_FALSE;
    }
    RETURN_RES(zend_register_resource(win, le_sdl_window));
}

PHP_FUNCTION(sdl_destroy_window) {
    zval *win_res;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &win_res) == FAILURE) {
        RETURN_THROWS();
    }

    // This will call the destructor and free the resource
    zend_list_close(Z_RES_P(win_res));
    RETURN_TRUE;
}

PHP_FUNCTION(sdl_destroy_renderer) {
    zval *ren_res;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &ren_res) == FAILURE) {
        RETURN_THROWS();
    }

    // This will call the destructor and free the resource
    zend_list_close(Z_RES_P(ren_res));
    RETURN_TRUE;
}

PHP_FUNCTION(sdl_get_window_id) {
    zval *win_res;
    SDL_Window *win;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &win_res) == FAILURE) {
        RETURN_THROWS();
    }

    win = (SDL_Window *)zend_fetch_resource(Z_RES_P(win_res), "SDL_Window", le_sdl_window);
    if (!win) {
        RETURN_FALSE;
    }

    SDL_WindowID window_id = SDL_GetWindowID(win);
    RETURN_LONG(window_id);
}

PHP_FUNCTION(sdl_create_renderer) {
    zval *win_res;
    SDL_Window *win;
    char *renderer_name = NULL;
    size_t renderer_name_len = 0;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|s", &win_res, &renderer_name, &renderer_name_len) == FAILURE) {
        RETURN_THROWS();
    }

    win = (SDL_Window *)zend_fetch_resource(Z_RES_P(win_res), "SDL_Window", le_sdl_window);
    if (!win) {
        RETURN_FALSE;
    }

    SDL_Renderer *ren = SDL_CreateRenderer(win, renderer_name_len > 0 ? renderer_name : NULL);
    if (!ren) {
        RETURN_FALSE;
    }
    RETURN_RES(zend_register_resource(ren, le_sdl_renderer));
}

PHP_FUNCTION(sdl_get_num_render_drivers) {
    int num = SDL_GetNumRenderDrivers();
    RETURN_LONG(num);
}

PHP_FUNCTION(sdl_get_render_driver) {
    zend_long index;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) {
        RETURN_THROWS();
    }

    const char *name = SDL_GetRenderDriver((int)index);
    if (!name) {
        RETURN_FALSE;
    }
    RETURN_STRING(name);
}

PHP_FUNCTION(sdl_set_render_draw_color) {
    zval *ren_res;
    SDL_Renderer *ren;
    zend_long r, g, b, a;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &ren_res, &r, &g, &b, &a) == FAILURE) {
        RETURN_THROWS();
    }

    ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!ren) {
        RETURN_FALSE;
    }

    SDL_SetRenderDrawColor(ren, (Uint8)r, (Uint8)g, (Uint8)b, (Uint8)a);
    RETURN_TRUE;
}

PHP_FUNCTION(sdl_render_clear) {
    zval *ren_res;
    SDL_Renderer *ren;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &ren_res) == FAILURE) {
        RETURN_THROWS();
    }

    ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!ren) {
        RETURN_FALSE;
    }

    SDL_RenderClear(ren);
    RETURN_TRUE;
}

PHP_FUNCTION(sdl_render_fill_rect) {
    zval *ren_res;
    SDL_Renderer *ren;
    zval *rect_arr;
    HashTable *rect_ht;
    zval *data;
    zend_long x, y, w, h;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra", &ren_res, &rect_arr) == FAILURE) {
        RETURN_THROWS();
    }

    ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!ren) {
        RETURN_FALSE;
    }

    rect_ht = Z_ARRVAL_P(rect_arr);
    if (((data = zend_hash_str_find(rect_ht, "x", 1)) != NULL && (x = zval_get_long(data), true)) &&
        ((data = zend_hash_str_find(rect_ht, "y", 1)) != NULL && (y = zval_get_long(data), true)) &&
        ((data = zend_hash_str_find(rect_ht, "w", 1)) != NULL && (w = zval_get_long(data), true)) &&
        ((data = zend_hash_str_find(rect_ht, "h", 1)) != NULL && (h = zval_get_long(data), true))) {

        SDL_FRect rect = {(float)x, (float)y, (float)w, (float)h};
        SDL_RenderFillRect(ren, &rect);
        RETURN_TRUE;
    }
    
    zend_throw_error(NULL, "Invalid rectangle array passed to sdl_render_fill_rect. Expected ['x'=>int, 'y'=>int, 'w'=>int, 'h'=>int]");
    RETURN_THROWS();
}

PHP_FUNCTION(sdl_render_rect) {
    zval *ren_res;
    SDL_Renderer *ren;
    zval *rect_arr;
    HashTable *rect_ht;
    zval *data;
    zend_long x, y, w, h;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra", &ren_res, &rect_arr) == FAILURE) {
        RETURN_THROWS();
    }

    ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!ren) {
        RETURN_FALSE;
    }

    rect_ht = Z_ARRVAL_P(rect_arr);
    if (((data = zend_hash_str_find(rect_ht, "x", 1)) != NULL && (x = zval_get_long(data), true)) &&
        ((data = zend_hash_str_find(rect_ht, "y", 1)) != NULL && (y = zval_get_long(data), true)) &&
        ((data = zend_hash_str_find(rect_ht, "w", 1)) != NULL && (w = zval_get_long(data), true)) &&
        ((data = zend_hash_str_find(rect_ht, "h", 1)) != NULL && (h = zval_get_long(data), true))) {

        SDL_FRect rect = {(float)x, (float)y, (float)w, (float)h};
        SDL_RenderRect(ren, &rect);
        RETURN_TRUE;
    }

    zend_throw_error(NULL, "Invalid rectangle array passed to sdl_render_rect. Expected ['x'=>int, 'y'=>int, 'w'=>int, 'h'=>int]");
    RETURN_THROWS();
}

PHP_FUNCTION(sdl_render_present) {
    zval *ren_res;
    SDL_Renderer *ren;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &ren_res) == FAILURE) {
        RETURN_THROWS();
    }

    ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!ren) {
        RETURN_FALSE;
    }

    SDL_RenderPresent(ren);
    RETURN_TRUE;
}

PHP_FUNCTION(sdl_delay) {
    zend_long ms;
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &ms) == FAILURE) {
        RETURN_THROWS();
    }
    SDL_Delay((Uint32)ms);
}

PHP_FUNCTION(sdl_get_error) {
    const char *error = SDL_GetError();
    RETURN_STRING(error);
}

PHP_FUNCTION(sdl_create_texture_from_surface) {
    zval *ren_res, *surf_res;
    SDL_Renderer *renderer;
    SDL_Surface *surface;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &ren_res, &surf_res) == FAILURE) {
        RETURN_THROWS();
    }

    renderer = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!renderer) {
        RETURN_FALSE;
    }

    surface = (SDL_Surface *)zend_fetch_resource(Z_RES_P(surf_res), "SDL_Surface", le_sdl_surface);
    if (!surface) {
        RETURN_FALSE;
    }

    SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
    if (!texture) {
        php_error_docref(NULL, E_WARNING, "Failed to create texture: %s", SDL_GetError());
        RETURN_FALSE;
    }

    RETURN_RES(zend_register_resource(texture, le_sdl_texture));
}

PHP_FUNCTION(sdl_render_texture) {
    zval *ren_res, *tex_res;
    SDL_Renderer *renderer;
    SDL_Texture *texture;
    zval *dst_arr = NULL;
    HashTable *dst_ht;
    zval *data;
    zend_long x, y, w, h;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr|a", &ren_res, &tex_res, &dst_arr) == FAILURE) {
        RETURN_THROWS();
    }

    renderer = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!renderer) {
        RETURN_FALSE;
    }

    texture = (SDL_Texture *)zend_fetch_resource(Z_RES_P(tex_res), "SDL_Texture", le_sdl_texture);
    if (!texture) {
        RETURN_FALSE;
    }

    if (dst_arr != NULL) {
        // Mit Zielrechteck
        dst_ht = Z_ARRVAL_P(dst_arr);
        if (((data = zend_hash_str_find(dst_ht, "x", 1)) != NULL && (x = zval_get_long(data), true)) &&
            ((data = zend_hash_str_find(dst_ht, "y", 1)) != NULL && (y = zval_get_long(data), true)) &&
            ((data = zend_hash_str_find(dst_ht, "w", 1)) != NULL && (w = zval_get_long(data), true)) &&
            ((data = zend_hash_str_find(dst_ht, "h", 1)) != NULL && (h = zval_get_long(data), true))) {

            SDL_FRect dstrect = {(float)x, (float)y, (float)w, (float)h};
            SDL_RenderTexture(renderer, texture, NULL, &dstrect);
            RETURN_TRUE;
        }

        zend_throw_error(NULL, "Invalid destination rectangle. Expected ['x'=>int, 'y'=>int, 'w'=>int, 'h'=>int]");
        RETURN_THROWS();
    } else {
        // Ohne Zielrechteck - volle Größe
        SDL_RenderTexture(renderer, texture, NULL, NULL);
        RETURN_TRUE;
    }
}

PHP_FUNCTION(sdl_create_texture) {
    zval *ren_res;
    SDL_Renderer *renderer;
    zend_long format, access, width, height;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &ren_res, &format, &access, &width, &height) == FAILURE) {
        RETURN_THROWS();
    }

    renderer = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!renderer) {
        RETURN_FALSE;
    }

    SDL_Texture *texture = SDL_CreateTexture(renderer, (Uint32)format, (int)access, (int)width, (int)height);
    if (!texture) {
        php_error_docref(NULL, E_WARNING, "Failed to create texture: %s", SDL_GetError());
        RETURN_FALSE;
    }

    RETURN_RES(zend_register_resource(texture, le_sdl_texture));
}

PHP_FUNCTION(sdl_destroy_texture) {
    zval *tex_res;
    SDL_Texture *texture;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &tex_res) == FAILURE) {
        RETURN_THROWS();
    }

    texture = (SDL_Texture *)zend_fetch_resource(Z_RES_P(tex_res), "SDL_Texture", le_sdl_texture);
    if (!texture) {
        RETURN_FALSE;
    }

    // Delete the resource to trigger the destructor
    zend_list_close(Z_RES_P(tex_res));
    RETURN_TRUE;
}

PHP_FUNCTION(sdl_update_texture) {
    zval *tex_res, *rect_arr, *pixels_arr;
    SDL_Texture *texture;
    SDL_Rect *rect = NULL, tmp_rect;
    zend_long pitch;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra!al", &tex_res, &rect_arr, &pixels_arr, &pitch) == FAILURE) {
        RETURN_THROWS();
    }

    texture = (SDL_Texture *)zend_fetch_resource(Z_RES_P(tex_res), "SDL_Texture", le_sdl_texture);
    if (!texture) {
        RETURN_FALSE;
    }

    // Parse rect if provided
    if (rect_arr != NULL && Z_TYPE_P(rect_arr) == IS_ARRAY) {
        HashTable *rect_ht = Z_ARRVAL_P(rect_arr);
        zval *val;

        if ((val = zend_hash_str_find(rect_ht, "x", sizeof("x")-1)) != NULL) {
            tmp_rect.x = (int)zval_get_long(val);
        }
        if ((val = zend_hash_str_find(rect_ht, "y", sizeof("y")-1)) != NULL) {
            tmp_rect.y = (int)zval_get_long(val);
        }
        if ((val = zend_hash_str_find(rect_ht, "w", sizeof("w")-1)) != NULL) {
            tmp_rect.w = (int)zval_get_long(val);
        }
        if ((val = zend_hash_str_find(rect_ht, "h", sizeof("h")-1)) != NULL) {
            tmp_rect.h = (int)zval_get_long(val);
        }
        rect = &tmp_rect;
    }

    // Convert PHP array to pixel data
    HashTable *pixels_ht = Z_ARRVAL_P(pixels_arr);
    int pixel_count = zend_hash_num_elements(pixels_ht);
    Uint32 *pixels = emalloc(pixel_count * sizeof(Uint32));

    zval *pixel_val;
    int i = 0;
    ZEND_HASH_FOREACH_VAL(pixels_ht, pixel_val) {
        pixels[i++] = (Uint32)zval_get_long(pixel_val);
    } ZEND_HASH_FOREACH_END();

    // Update texture
    if (SDL_UpdateTexture(texture, rect, pixels, (int)pitch) < 0) {
        efree(pixels);
        php_error_docref(NULL, E_WARNING, "Failed to update texture: %s", SDL_GetError());
        RETURN_FALSE;
    }

    efree(pixels);
    RETURN_TRUE;
}

PHP_FUNCTION(sdl_set_texture_blend_mode) {
    zval *tex_res;
    SDL_Texture *texture;
    zend_long blend_mode;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &tex_res, &blend_mode) == FAILURE) {
        RETURN_THROWS();
    }

    texture = (SDL_Texture *)zend_fetch_resource(Z_RES_P(tex_res), "SDL_Texture", le_sdl_texture);
    if (!texture) {
        RETURN_FALSE;
    }

    if (SDL_SetTextureBlendMode(texture, (SDL_BlendMode)blend_mode) < 0) {
        php_error_docref(NULL, E_WARNING, "Failed to set texture blend mode: %s", SDL_GetError());
        RETURN_FALSE;
    }

    RETURN_TRUE;
}

PHP_FUNCTION(sdl_set_texture_alpha_mod) {
    zval *tex_res;
    SDL_Texture *texture;
    zend_long alpha;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &tex_res, &alpha) == FAILURE) {
        RETURN_THROWS();
    }

    texture = (SDL_Texture *)zend_fetch_resource(Z_RES_P(tex_res), "SDL_Texture", le_sdl_texture);
    if (!texture) {
        RETURN_FALSE;
    }

    if (SDL_SetTextureAlphaMod(texture, (Uint8)alpha) < 0) {
        php_error_docref(NULL, E_WARNING, "Failed to set texture alpha mod: %s", SDL_GetError());
        RETURN_FALSE;
    }

    RETURN_TRUE;
}

PHP_FUNCTION(tray_setup)
{
    char *icon_path;
    size_t icon_len;
    zval *menu_arr = NULL;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|a", &icon_path, &icon_len, &menu_arr) == FAILURE) {
        RETURN_THROWS();
    }

    // Clean up existing tray if any
    if (g_sdl_tray) {
        SDL_DestroyTray(g_sdl_tray);
        g_sdl_tray = NULL;
        g_sdl_tray_menu = NULL;
    }
    if (g_sdl_tray_entries) {
        efree(g_sdl_tray_entries);
        g_sdl_tray_entries = NULL;
        g_sdl_tray_entry_count = 0;
    }
    if (g_tray_callbacks) {
        // Free old callbacks
        for (int i = 0; i < g_sdl_tray_entry_count; i++) {
            zval_ptr_dtor(&g_tray_callbacks[i]);
        }
        efree(g_tray_callbacks);
        g_tray_callbacks = NULL;
    }

    // Load icon if provided (optional)
    SDL_Surface *icon_surface = NULL;
    if (icon_len > 0) {
        icon_surface = SDL_LoadBMP(icon_path);
        // Icon can be NULL, SDL will handle it
    }

    // Initialize video subsystem if not already initialized (required for tray)
    if (!SDL_WasInit(SDL_INIT_VIDEO)) {
        if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) {
            if (icon_surface) {
                SDL_DestroySurface(icon_surface);
            }
            php_error_docref(NULL, E_WARNING, "Failed to init video subsystem for tray: %s", SDL_GetError());
            RETURN_FALSE;
        }
    }

    // Check if DISPLAY is set (required for GTK-based tray on Linux)
    #ifdef __linux__
    const char *display = getenv("DISPLAY");
    const char *wayland = getenv("WAYLAND_DISPLAY");
    if (!display && !wayland) {
        if (icon_surface) {
            SDL_DestroySurface(icon_surface);
        }
        php_error_docref(NULL, E_WARNING, "Cannot create tray: No DISPLAY or WAYLAND_DISPLAY environment variable set");
        RETURN_FALSE;
    }
    #endif

    // Create SDL3 tray
    g_sdl_tray = SDL_CreateTray(icon_surface, "PHP SDL3 Tray");

    if (icon_surface) {
        SDL_DestroySurface(icon_surface);
    }

    if (!g_sdl_tray) {
        php_error_docref(NULL, E_WARNING, "Failed to create tray: %s", SDL_GetError());
        RETURN_FALSE;
    }

    // Create tray menu
    g_sdl_tray_menu = SDL_CreateTrayMenu(g_sdl_tray);
    if (!g_sdl_tray_menu) {
        SDL_DestroyTray(g_sdl_tray);
        g_sdl_tray = NULL;
        php_error_docref(NULL, E_WARNING, "Failed to create tray menu: %s", SDL_GetError());
        RETURN_FALSE;
    }

    // Add menu items if provided
    if (menu_arr && Z_TYPE_P(menu_arr) == IS_ARRAY) {
        HashTable *ht = Z_ARRVAL_P(menu_arr);
        int count = zend_hash_num_elements(ht);

        if (count > 0) {
            g_sdl_tray_entries = ecalloc(count, sizeof(SDL_TrayEntry *));
            g_tray_callbacks = ecalloc(count, sizeof(zval));
            g_sdl_tray_entry_count = count;

            int idx = 0;
            zval *val;
            ZEND_HASH_FOREACH_VAL(ht, val) {
                if (idx >= count) {
                    break;
                }

                const char *label = NULL;
                zval *callback = NULL;

                // Handle array entries: ['label' => '...', 'callback' => function]
                if (Z_TYPE_P(val) == IS_ARRAY) {
                    zval *label_val = zend_hash_str_find(Z_ARRVAL_P(val), "label", sizeof("label") - 1);
                    zval *callback_val = zend_hash_str_find(Z_ARRVAL_P(val), "callback", sizeof("callback") - 1);

                    if (label_val && Z_TYPE_P(label_val) == IS_STRING) {
                        label = Z_STRVAL_P(label_val);
                    }

                    if (callback_val && zend_is_callable(callback_val, 0, NULL)) {
                        callback = callback_val;
                    }
                }
                // Handle simple string entries (backward compatibility)
                else if (Z_TYPE_P(val) == IS_STRING) {
                    label = Z_STRVAL_P(val);
                }

                // Create entry (NULL label creates separator)
                SDL_TrayEntry *entry = SDL_InsertTrayEntryAt(
                    g_sdl_tray_menu,
                    -1,
                    label,
                    SDL_TRAYENTRY_BUTTON
                );

                if (!entry) {
                    php_error_docref(NULL, E_WARNING, "Failed to create tray entry %d ('%s'): %s", idx, label ? label : "(null)", SDL_GetError());
                }

                if (entry && label) {
                    // Set callback with index+1 as userdata (so index 0 doesn't become NULL)
                    SDL_SetTrayEntryCallback(entry, php_tray_callback, (void *)(intptr_t)(idx + 1));
                    php_error_docref(NULL, E_NOTICE, "Registered tray entry %d: '%s' with callback=%s", idx, label, callback ? "YES" : "NO");
                }

                // Store PHP callback if provided
                if (callback) {
                    ZVAL_COPY(&g_tray_callbacks[idx], callback);
                } else {
                    ZVAL_UNDEF(&g_tray_callbacks[idx]);
                }

                g_sdl_tray_entries[idx] = entry;
                idx++;
            } ZEND_HASH_FOREACH_END();
        }
    }

    RETURN_TRUE;
}

PHP_FUNCTION(tray_poll)
{
    zend_bool blocking = 0;
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &blocking) == FAILURE) {
        RETURN_THROWS();
    }

    if (!g_sdl_tray) {
        RETURN_LONG(-1);
    }

    // SDL_UpdateTrays() processes events that were already polled by sdl_poll_event()
    // The event polling happens in the Application loop before this is called
    // SDL_UpdateTrays() will trigger our C callbacks, which call the PHP callbacks
    SDL_UpdateTrays();

    // Always return -1 (events are handled via callbacks)
    RETURN_LONG(-1);
}

PHP_FUNCTION(tray_exit)
{
    if (zend_parse_parameters_none() == FAILURE) {
        RETURN_THROWS();
    }

    if (g_sdl_tray) {
        SDL_DestroyTray(g_sdl_tray);
        g_sdl_tray = NULL;
        g_sdl_tray_menu = NULL;
    }

    if (g_sdl_tray_entries) {
        efree(g_sdl_tray_entries);
        g_sdl_tray_entries = NULL;
    }

    if (g_tray_callbacks) {
        // Free callbacks
        for (int i = 0; i < g_sdl_tray_entry_count; i++) {
            zval_ptr_dtor(&g_tray_callbacks[i]);
        }
        efree(g_tray_callbacks);
        g_tray_callbacks = NULL;
    }

    g_sdl_tray_entry_count = 0;

    RETURN_TRUE;
}

PHP_FUNCTION(desktop_notify)
{
    char *title, *body;
    size_t title_len, body_len;
    zval *options = NULL;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|a", &title, &title_len, &body, &body_len, &options) == FAILURE) {
        RETURN_THROWS();
    }

#ifdef HAVE_LIBNOTIFY
    if (!notify_is_initted()) {
        if (!notify_init("PHPNative")) {
            php_error_docref(NULL, E_WARNING, "Failed to initialize libnotify");
            RETURN_FALSE;
        }
    }

    NotifyNotification *n = notify_notification_new(title, body, NULL);
    if (!n) {
        php_error_docref(NULL, E_WARNING, "Failed to create notification");
        RETURN_FALSE;
    }

    if (options && Z_TYPE_P(options) == IS_ARRAY) {
        zval *timeout = zend_hash_str_find(Z_ARRVAL_P(options), "timeout", sizeof("timeout") - 1);
        if (timeout && Z_TYPE_P(timeout) == IS_LONG) {
            notify_notification_set_timeout(n, (int) Z_LVAL_P(timeout));
        }

        zval *urgency = zend_hash_str_find(Z_ARRVAL_P(options), "urgency", sizeof("urgency") - 1);
        if (urgency && Z_TYPE_P(urgency) == IS_STRING) {
            const char *u = Z_STRVAL_P(urgency);
            if (strcmp(u, "low") == 0) {
                notify_notification_set_urgency(n, NOTIFY_URGENCY_LOW);
            } else if (strcmp(u, "critical") == 0) {
                notify_notification_set_urgency(n, NOTIFY_URGENCY_CRITICAL);
            } else {
                notify_notification_set_urgency(n, NOTIFY_URGENCY_NORMAL);
            }
        }
    }

    GError *error = NULL;
    gboolean res = notify_notification_show(n, &error);
    if (!res) {
        if (error) {
            php_error_docref(NULL, E_WARNING, "Notification error: %s", error->message);
            g_error_free(error);
        }
        g_object_unref(G_OBJECT(n));
        RETURN_FALSE;
    }

    g_object_unref(G_OBJECT(n));
    RETURN_TRUE;
#else
    php_error_docref(NULL, E_WARNING, "desktop_notify() not available (libnotify not found at build time)");
    RETURN_FALSE;
#endif
}

PHP_FUNCTION(sdl_create_box_shadow_texture) {
    zval *ren_res;
    SDL_Renderer *renderer;
    zend_long width, height, blurRadius, alpha, r, g, b;

    if (zend_parse_parameters(
            ZEND_NUM_ARGS(),
            "rlllllll",
            &ren_res,
            &width,
            &height,
            &blurRadius,
            &alpha,
            &r,
            &g,
            &b) == FAILURE) {
        RETURN_THROWS();
    }

    renderer = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!renderer) {
        RETURN_FALSE;
    }

    if (width <= 0 || height <= 0) {
        RETURN_FALSE;
    }

    int w = (int) width;
    int h = (int) height;
    int radius = (int) blurRadius;
    if (radius < 0) {
        radius = 0;
    }
    if (alpha < 0) {
        alpha = 0;
    }
    if (alpha > 255) {
        alpha = 255;
    }

    int size = w * h;
    Uint8 *alphaMap = emalloc(size * sizeof(Uint8));
    if (!alphaMap) {
        RETURN_FALSE;
    }

    memset(alphaMap, 0, size * sizeof(Uint8));

    int margin = radius + 2;
    for (int y = margin; y < h - margin; y++) {
        int rowOffset = y * w;
        for (int x = margin; x < w - margin; x++) {
            alphaMap[rowOffset + x] = (Uint8) alpha;
        }
    }

    if (radius > 0) {
        Uint8 *temp = emalloc(size * sizeof(Uint8));
        if (!temp) {
            efree(alphaMap);
            RETURN_FALSE;
        }

        // Horizontal blur
        for (int y = 0; y < h; y++) {
            int rowOffset = y * w;
            for (int x = 0; x < w; x++) {
                int sum = 0;
                int count = 0;
                int xStart = x - radius;
                int xEnd = x + radius;
                if (xStart < 0) xStart = 0;
                if (xEnd >= w) xEnd = w - 1;
                for (int xi = xStart; xi <= xEnd; xi++) {
                    sum += alphaMap[rowOffset + xi];
                    count++;
                }
                temp[rowOffset + x] = count > 0 ? (Uint8)(sum / count) : 0;
            }
        }

        // Vertical blur
        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                int sum = 0;
                int count = 0;
                int yStart = y - radius;
                int yEnd = y + radius;
                if (yStart < 0) yStart = 0;
                if (yEnd >= h) yEnd = h - 1;
                for (int yi = yStart; yi <= yEnd; yi++) {
                    sum += temp[yi * w + x];
                    count++;
                }
                alphaMap[y * w + x] = count > 0 ? (Uint8)(sum / count) : 0;
            }
        }

        efree(temp);
    }

    SDL_Texture *texture = SDL_CreateTexture(
        renderer,
        SDL_PIXELFORMAT_ARGB8888,
        SDL_TEXTUREACCESS_STATIC,
        w,
        h);

    if (!texture) {
        efree(alphaMap);
        php_error_docref(NULL, E_WARNING, "Failed to create shadow texture: %s", SDL_GetError());
        RETURN_FALSE;
    }

    Uint32 *pixels = emalloc(size * sizeof(Uint32));
    if (!pixels) {
        efree(alphaMap);
        SDL_DestroyTexture(texture);
        RETURN_FALSE;
    }

    Uint8 red = (Uint8) r;
    Uint8 green = (Uint8) g;
    Uint8 blue = (Uint8) b;

    for (int i = 0; i < size; i++) {
        Uint8 a = alphaMap[i];
        pixels[i] = ((Uint32)a << 24) | ((Uint32)red << 16) | ((Uint32)green << 8) | (Uint32)blue;
    }

    if (SDL_UpdateTexture(texture, NULL, pixels, w * 4) < 0) {
        efree(alphaMap);
        efree(pixels);
        SDL_DestroyTexture(texture);
        php_error_docref(NULL, E_WARNING, "Failed to update shadow texture: %s", SDL_GetError());
        RETURN_FALSE;
    }

    efree(alphaMap);
    efree(pixels);

    SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);

    RETURN_RES(zend_register_resource(texture, le_sdl_texture));
}

PHP_FUNCTION(sdl_get_render_target) {
    zval *ren_res;
    SDL_Renderer *renderer;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &ren_res) == FAILURE) {
        RETURN_THROWS();
    }

    renderer = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!renderer) {
        RETURN_FALSE;
    }

    SDL_Texture *texture = SDL_GetRenderTarget(renderer);
    if (!texture) {
        // NULL is valid - means rendering to the screen
        RETURN_NULL();
    }

    // Return the existing texture resource (don't register a new one)
    // We just return the texture pointer as a resource
    RETURN_RES(zend_register_resource(texture, le_sdl_texture));
}

PHP_FUNCTION(sdl_set_render_target) {
    zval *ren_res;
    zval *tex_res = NULL;
    SDL_Renderer *renderer;
    SDL_Texture *texture = NULL;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|r!", &ren_res, &tex_res) == FAILURE) {
        RETURN_THROWS();
    }

    renderer = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!renderer) {
        RETURN_FALSE;
    }

    if (tex_res != NULL && Z_TYPE_P(tex_res) == IS_RESOURCE) {
        texture = (SDL_Texture *)zend_fetch_resource(Z_RES_P(tex_res), "SDL_Texture", le_sdl_texture);
        if (!texture) {
            RETURN_FALSE;
        }
    }

    if (SDL_SetRenderTarget(renderer, texture) < 0) {
        php_error_docref(NULL, E_WARNING, "Failed to set render target: %s", SDL_GetError());
        RETURN_FALSE;
    }

    RETURN_TRUE;
}

PHP_FUNCTION(sdl_rounded_box)
{
    zval *ren_res;
    SDL_Renderer *ren;
    zend_long x1, y1, x2, y2, rad;
    zend_long r, g, b, a;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllllllll", &ren_res, &x1, &y1, &x2, &y2, &rad, &r, &g, &b, &a) == FAILURE) {
        RETURN_THROWS();
    }

    ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!ren) {
        RETURN_FALSE;
    }

    // Zeichne eine Box mit gleichen Radien an allen Ecken (Wrapper um die erweiterte Variante)
    SDL_SetRenderDrawColor(ren, (Uint8)r, (Uint8)g, (Uint8)b, (Uint8)a);

    int halfw = ((Sint16)x2 - (Sint16)x1) / 2;
    int halfh = ((Sint16)y2 - (Sint16)y1) / 2;
    int rad_tl = (int)rad;
    int rad_tr = (int)rad;
    int rad_br = (int)rad;
    int rad_bl = (int)rad;

    if (rad_tl > halfw) rad_tl = halfw; if (rad_tl > halfh) rad_tl = halfh;
    if (rad_tr > halfw) rad_tr = halfw; if (rad_tr > halfh) rad_tr = halfh;
    if (rad_br > halfw) rad_br = halfw; if (rad_br > halfh) rad_br = halfh;
    if (rad_bl > halfw) rad_bl = halfw; if (rad_bl > halfh) rad_bl = halfh;

    SDL_FRect topRect = { x1 + rad_tl, y1, x2 - x1 - rad_tl - rad_tr, rad_tl > rad_tr ? rad_tl : rad_tr };
    if (topRect.w > 0 && topRect.h > 0) SDL_RenderFillRect(ren, &topRect);

    int maxBottomRad = rad_bl > rad_br ? rad_bl : rad_br;
    SDL_FRect bottomRect = { x1 + rad_bl, y2 - maxBottomRad, x2 - x1 - rad_bl - rad_br, maxBottomRad };
    if (bottomRect.w > 0 && bottomRect.h > 0) SDL_RenderFillRect(ren, &bottomRect);

    SDL_FRect leftRect = { x1, y1 + rad_tl, rad_tl > rad_bl ? rad_tl : rad_bl, y2 - y1 - rad_tl - rad_bl };
    if (leftRect.w > 0 && leftRect.h > 0) SDL_RenderFillRect(ren, &leftRect);

    int maxRightRad = rad_tr > rad_br ? rad_tr : rad_br;
    SDL_FRect rightRect = { x2 - maxRightRad, y1 + rad_tr, maxRightRad, y2 - y1 - rad_tr - rad_br };
    if (rightRect.w > 0 && rightRect.h > 0) SDL_RenderFillRect(ren, &rightRect);

    int maxLeftRad = rad_tl > rad_bl ? rad_tl : rad_bl;
    maxRightRad = rad_tr > rad_br ? rad_tr : rad_br;
    SDL_FRect centerRect = { x1 + maxLeftRad, y1, x2 - x1 - maxLeftRad - maxRightRad, y2 - y1 };
    if (centerRect.w > 0 && centerRect.h > 0) SDL_RenderFillRect(ren, &centerRect);

    if (rad_tl > 0) filled_quarter_circle(ren, x1 + rad_tl, y1 + rad_tl, rad_tl, 0);
    if (rad_tr > 0) filled_quarter_circle(ren, x2 - rad_tr - 1, y1 + rad_tr, rad_tr, 1);
    if (rad_br > 0) filled_quarter_circle(ren, x2 - rad_br - 1, y2 - rad_br - 1, rad_br, 2);
    if (rad_bl > 0) filled_quarter_circle(ren, x1 + rad_bl, y2 - rad_bl - 1, rad_bl, 3);

    RETURN_TRUE;
}

#include <math.h>

PHP_FUNCTION(sdl_rounded_box_ex)
{
    zval *ren_res;
    SDL_Renderer *ren;
    zend_long x1, y1, x2, y2;
    zend_long rad_tl, rad_tr, rad_br, rad_bl;
    zend_long r, g, b, a;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllllllllllll", &ren_res, &x1, &y1, &x2, &y2, &rad_tl, &rad_tr, &rad_br, &rad_bl, &r, &g, &b, &a) == FAILURE) {
        RETURN_THROWS();
    }

    ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!ren) RETURN_FALSE;

    SDL_SetRenderDrawColor(ren, r, g, b, a);

    int halfw = ((Sint16)x2-(Sint16)x1) / 2;
    int halfh = ((Sint16)y2-(Sint16)y1) / 2;
    if (rad_tl > halfw) rad_tl = halfw; if (rad_tl > halfh) rad_tl = halfh;
    if (rad_tr > halfw) rad_tr = halfw; if (rad_tr > halfh) rad_tr = halfh;
    if (rad_br > halfw) rad_br = halfw; if (rad_br > halfh) rad_br = halfh;
    if (rad_bl > halfw) rad_bl = halfw; if (rad_bl > halfh) rad_bl = halfh;

    // Einfachere Strategie: Zeichne Rechtecke die sich überlappen dürfen,
    // dann die Kreise darüber

    // 1) Horizontales Rechteck oben (von linker Ecke bis rechter Ecke)
    SDL_FRect topRect = { x1 + rad_tl, y1, x2 - x1 - rad_tl - rad_tr, rad_tl > rad_tr ? rad_tl : rad_tr };
    if (topRect.w > 0 && topRect.h > 0) SDL_RenderFillRect(ren, &topRect);

    // 2) Horizontales Rechteck unten (von linker Ecke bis rechter Ecke)
    int maxBottomRad = rad_bl > rad_br ? rad_bl : rad_br;
    SDL_FRect bottomRect = { x1 + rad_bl, y2 - maxBottomRad, x2 - x1 - rad_bl - rad_br, maxBottomRad };
    if (bottomRect.w > 0 && bottomRect.h > 0) SDL_RenderFillRect(ren, &bottomRect);

    // 3) Vertikales Rechteck links (volle Höhe zwischen Ecken)
    SDL_FRect leftRect = { x1, y1 + rad_tl, rad_tl > rad_bl ? rad_tl : rad_bl, y2 - y1 - rad_tl - rad_bl };
    if (leftRect.w > 0 && leftRect.h > 0) SDL_RenderFillRect(ren, &leftRect);

    // 4) Vertikales Rechteck rechts (volle Höhe zwischen Ecken)
    int maxRightRad = rad_tr > rad_br ? rad_tr : rad_br;
    SDL_FRect rightRect = { x2 - maxRightRad, y1 + rad_tr, maxRightRad, y2 - y1 - rad_tr - rad_br };
    if (rightRect.w > 0 && rightRect.h > 0) SDL_RenderFillRect(ren, &rightRect);

    // 5) Zentrales Rechteck (füllt die Mitte)
    int maxLeftRad = rad_tl > rad_bl ? rad_tl : rad_bl;
    maxRightRad = rad_tr > rad_br ? rad_tr : rad_br;
    SDL_FRect centerRect = { x1 + maxLeftRad, y1, x2 - x1 - maxLeftRad - maxRightRad, y2 - y1 };
    if (centerRect.w > 0 && centerRect.h > 0) SDL_RenderFillRect(ren, &centerRect);

    // 6) vier gefüllte Viertel-Kreise in den Ecken (darüber zeichnen)
    if (rad_tl > 0) filled_quarter_circle(ren, x1 + rad_tl, y1 + rad_tl, rad_tl, 0);
    if (rad_tr > 0) filled_quarter_circle(ren, x2 - rad_tr - 1, y1 + rad_tr, rad_tr, 1);
    if (rad_br > 0) filled_quarter_circle(ren, x2 - rad_br - 1, y2 - rad_br - 1, rad_br, 2);
    if (rad_bl > 0) filled_quarter_circle(ren, x1 + rad_bl, y2 - rad_bl - 1, rad_bl, 3); 

    RETURN_TRUE;
}

PHP_FUNCTION(sdl_set_render_clip_rect) {
    zval *ren_res;
    SDL_Renderer *ren;
    zval *rect_arr = NULL;
    HashTable *rect_ht;
    zval *data;
    zend_long x, y, w, h;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|a!", &ren_res, &rect_arr) == FAILURE) {
        RETURN_THROWS();
    }

    ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!ren) {
        RETURN_FALSE;
    }

    if (rect_arr == NULL || Z_TYPE_P(rect_arr) == IS_NULL) {
        // Disable clipping
        SDL_SetRenderClipRect(ren, NULL);
        RETURN_TRUE;
    }

    rect_ht = Z_ARRVAL_P(rect_arr);
    if (((data = zend_hash_str_find(rect_ht, "x", 1)) != NULL && (x = zval_get_long(data), true)) &&
        ((data = zend_hash_str_find(rect_ht, "y", 1)) != NULL && (y = zval_get_long(data), true)) &&
        ((data = zend_hash_str_find(rect_ht, "w", 1)) != NULL && (w = zval_get_long(data), true)) &&
        ((data = zend_hash_str_find(rect_ht, "h", 1)) != NULL && (h = zval_get_long(data), true))) {

        SDL_Rect rect = {(int)x, (int)y, (int)w, (int)h};
        SDL_SetRenderClipRect(ren, &rect);
        RETURN_TRUE;
    }

    zend_throw_error(NULL, "Invalid rectangle array passed to sdl_set_render_clip_rect. Expected ['x'=>int, 'y'=>int, 'w'=>int, 'h'=>int] or null");
    RETURN_THROWS();
}

PHP_FUNCTION(sdl_set_render_scale) {
    zval *ren_res;
    SDL_Renderer *ren;
    double scale_x, scale_y;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "rdd", &ren_res, &scale_x, &scale_y) == FAILURE) {
        RETURN_THROWS();
    }

    ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!ren) {
        RETURN_FALSE;
    }

    if (SDL_SetRenderScale(ren, (float)scale_x, (float)scale_y) < 0) {
        php_error_docref(NULL, E_WARNING, "Failed to set render scale: %s", SDL_GetError());
        RETURN_FALSE;
    }

    RETURN_TRUE;
}

PHP_FUNCTION(sdl_get_window_size) {
    zval *win_res;
    SDL_Window *win;
    int w, h;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &win_res) == FAILURE) {
        RETURN_THROWS();
    }

    win = (SDL_Window *)zend_fetch_resource(Z_RES_P(win_res), "SDL_Window", le_sdl_window);
    if (!win) {
        RETURN_FALSE;
    }

    SDL_GetWindowSize(win, &w, &h);

    array_init(return_value);
    add_index_long(return_value, 0, w);
    add_index_long(return_value, 1, h);
}

PHP_FUNCTION(sdl_get_window_size_in_pixels) {
    zval *win_res;
    SDL_Window *win;
    int w, h;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &win_res) == FAILURE) {
        RETURN_THROWS();
    }

    win = (SDL_Window *)zend_fetch_resource(Z_RES_P(win_res), "SDL_Window", le_sdl_window);
    if (!win) {
        RETURN_FALSE;
    }

    if (SDL_GetWindowSizeInPixels(win, &w, &h) < 0) {
        php_error_docref(NULL, E_WARNING, "Failed to get window size in pixels: %s", SDL_GetError());
        RETURN_FALSE;
    }

    array_init(return_value);
    add_index_long(return_value, 0, w);
    add_index_long(return_value, 1, h);
}

PHP_FUNCTION(sdl_get_window_pixel_density) {
    zval *win_res;
    SDL_Window *win;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &win_res) == FAILURE) {
        RETURN_THROWS();
    }

    win = (SDL_Window *)zend_fetch_resource(Z_RES_P(win_res), "SDL_Window", le_sdl_window);
    if (!win) {
        RETURN_FALSE;
    }

    float density = SDL_GetWindowPixelDensity(win);
    RETURN_DOUBLE(density);
}

PHP_FUNCTION(sdl_get_renderer_output_size) {
    zval *ren_res;
    SDL_Renderer *ren;
    int w, h;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &ren_res) == FAILURE) {
        RETURN_THROWS();
    }

    ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
    if (!ren) {
        RETURN_FALSE;
    }

    if (SDL_GetRenderOutputSize(ren, &w, &h) < 0) {
        php_error_docref(NULL, E_WARNING, "Failed to get renderer output size: %s", SDL_GetError());
        RETURN_FALSE;
    }

    array_init(return_value);
    add_index_long(return_value, 0, w);
    add_index_long(return_value, 1, h);
}

PHP_FUNCTION(sdl_get_window_display_scale) {
    zval *win_res;
    SDL_Window *win;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &win_res) == FAILURE) {
        RETURN_THROWS();
    }

    win = (SDL_Window *)zend_fetch_resource(Z_RES_P(win_res), "SDL_Window", le_sdl_window);
    if (!win) {
        RETURN_FALSE;
    }

    float scale = SDL_GetWindowDisplayScale(win);
    RETURN_DOUBLE(scale);
}

PHP_FUNCTION(sdl_get_display_content_scale) {
    zval *win_res;
    SDL_Window *win;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &win_res) == FAILURE) {
        RETURN_THROWS();
    }

    win = (SDL_Window *)zend_fetch_resource(Z_RES_P(win_res), "SDL_Window", le_sdl_window);
    if (!win) {
        RETURN_FALSE;
    }

    SDL_DisplayID display = SDL_GetDisplayForWindow(win);
    if (!display) {
        RETURN_FALSE;
    }

    float scale = SDL_GetDisplayContentScale(display);
    RETURN_DOUBLE(scale);
}

PHP_FUNCTION(sdl_get_current_video_driver) {
    const char *drv = SDL_GetCurrentVideoDriver();
    if (!drv) {
        RETURN_FALSE;
    }
    RETURN_STRING(drv);
}

PHP_FUNCTION(sdl_get_num_video_drivers) {
    int num = SDL_GetNumVideoDrivers();
    RETURN_LONG(num);
}

PHP_FUNCTION(sdl_get_video_driver) {
    zend_long index;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) {
        RETURN_THROWS();
    }

    const char *driver = SDL_GetVideoDriver((int)index);
    if (!driver) {
        RETURN_FALSE;
    }
    RETURN_STRING(driver);
}

PHP_FUNCTION(sdl_start_text_input) {
    zval *win_res;
    SDL_Window *win;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &win_res) == FAILURE) {
        RETURN_THROWS();
    }

    win = (SDL_Window *)zend_fetch_resource(Z_RES_P(win_res), "SDL_Window", le_sdl_window);
    if (!win) {
        RETURN_FALSE;
    }

    SDL_StartTextInput(win);
    RETURN_TRUE;
}

PHP_FUNCTION(sdl_stop_text_input) {
    zval *win_res;
    SDL_Window *win;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &win_res) == FAILURE) {
        RETURN_THROWS();
    }

    win = (SDL_Window *)zend_fetch_resource(Z_RES_P(win_res), "SDL_Window", le_sdl_window);
    if (!win) {
        RETURN_FALSE;
    }

    SDL_StopTextInput(win);
    RETURN_TRUE;
}

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_init, 0, 0, 1)
    ZEND_ARG_INFO(0, flags)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_quit, 0, 0, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_create_window, 0, 0, 3)
    ZEND_ARG_INFO(0, title)
    ZEND_ARG_INFO(0, w)
    ZEND_ARG_INFO(0, h)
    ZEND_ARG_INFO(0, flags)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_destroy_window, 0, 0, 1)
    ZEND_ARG_INFO(0, window)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_destroy_renderer, 0, 0, 1)
    ZEND_ARG_INFO(0, renderer)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_window_id, 0, 0, 1)
    ZEND_ARG_INFO(0, window)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_create_renderer, 0, 0, 1)
    ZEND_ARG_INFO(0, window)
    ZEND_ARG_INFO(0, renderer_name)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_num_render_drivers, 0, 0, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_render_driver, 0, 0, 1)
    ZEND_ARG_INFO(0, index)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_render_draw_color, 0, 0, 5)
    ZEND_ARG_INFO(0, renderer)
    ZEND_ARG_INFO(0, r)
    ZEND_ARG_INFO(0, g)
    ZEND_ARG_INFO(0, b)
    ZEND_ARG_INFO(0, a)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_render_clear, 0, 0, 1)
    ZEND_ARG_INFO(0, renderer)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_render_fill_rect, 0, 0, 2)
    ZEND_ARG_INFO(0, renderer)
    ZEND_ARG_INFO(0, rect)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_render_rect, 0, 0, 2)
    ZEND_ARG_INFO(0, renderer)
    ZEND_ARG_INFO(0, rect)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_render_present, 0, 0, 1)
    ZEND_ARG_INFO(0, renderer)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_delay, 0, 0, 1)
    ZEND_ARG_INFO(0, ms)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_error, 0, 0, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_create_texture_from_surface, 0, 0, 2)
    ZEND_ARG_INFO(0, renderer)
    ZEND_ARG_INFO(0, surface)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_render_texture, 0, 0, 2)
    ZEND_ARG_INFO(0, renderer)
    ZEND_ARG_INFO(0, texture)
    ZEND_ARG_INFO(0, dstrect)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_create_texture, 0, 0, 5)
    ZEND_ARG_INFO(0, renderer)
    ZEND_ARG_INFO(0, format)
    ZEND_ARG_INFO(0, access)
    ZEND_ARG_INFO(0, width)
    ZEND_ARG_INFO(0, height)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_destroy_texture, 0, 0, 1)
    ZEND_ARG_INFO(0, texture)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_update_texture, 0, 0, 4)
    ZEND_ARG_INFO(0, texture)
    ZEND_ARG_INFO(0, rect)
    ZEND_ARG_ARRAY_INFO(0, pixels, 0)
    ZEND_ARG_INFO(0, pitch)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_texture_blend_mode, 0, 0, 2)
    ZEND_ARG_INFO(0, texture)
    ZEND_ARG_INFO(0, blend_mode)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_texture_alpha_mod, 0, 0, 2)
    ZEND_ARG_INFO(0, texture)
    ZEND_ARG_INFO(0, alpha)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_tray_setup, 0, 0, 1)
    ZEND_ARG_INFO(0, icon)
    ZEND_ARG_ARRAY_INFO(0, menuItems, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_tray_poll, 0, 0, 0)
    ZEND_ARG_INFO(0, blocking)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_tray_exit, 0, 0, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_desktop_notify, 0, 0, 2)
    ZEND_ARG_INFO(0, title)
    ZEND_ARG_INFO(0, body)
    ZEND_ARG_ARRAY_INFO(0, options, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_create_box_shadow_texture, 0, 0, 8)
    ZEND_ARG_INFO(0, renderer)
    ZEND_ARG_INFO(0, width)
    ZEND_ARG_INFO(0, height)
    ZEND_ARG_INFO(0, blurRadius)
    ZEND_ARG_INFO(0, alpha)
    ZEND_ARG_INFO(0, r)
    ZEND_ARG_INFO(0, g)
    ZEND_ARG_INFO(0, b)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_render_target, 0, 0, 1)
    ZEND_ARG_INFO(0, renderer)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_render_target, 0, 0, 1)
    ZEND_ARG_INFO(0, renderer)
    ZEND_ARG_INFO(0, texture)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_rounded_box, 0, 0, 10)
    ZEND_ARG_INFO(0, renderer)
    ZEND_ARG_INFO(0, x1)
    ZEND_ARG_INFO(0, y1)
    ZEND_ARG_INFO(0, x2)
    ZEND_ARG_INFO(0, y2)
    ZEND_ARG_INFO(0, radius)
    ZEND_ARG_INFO(0, r)
    ZEND_ARG_INFO(0, g)
    ZEND_ARG_INFO(0, b)
    ZEND_ARG_INFO(0, a)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_rounded_box_ex, 0, 0, 13)
    ZEND_ARG_INFO(0, renderer)
    ZEND_ARG_INFO(0, x1)
    ZEND_ARG_INFO(0, y1)
    ZEND_ARG_INFO(0, x2)
    ZEND_ARG_INFO(0, y2)
    ZEND_ARG_INFO(0, rad_tl)
    ZEND_ARG_INFO(0, rad_tr)
    ZEND_ARG_INFO(0, rad_br)
    ZEND_ARG_INFO(0, rad_bl)
    ZEND_ARG_INFO(0, r)
    ZEND_ARG_INFO(0, g)
    ZEND_ARG_INFO(0, b)
    ZEND_ARG_INFO(0, a)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_render_clip_rect, 0, 0, 1)
    ZEND_ARG_INFO(0, renderer)
    ZEND_ARG_INFO(0, rect)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_render_scale, 0, 0, 3)
    ZEND_ARG_INFO(0, renderer)
    ZEND_ARG_INFO(0, scaleX)
    ZEND_ARG_INFO(0, scaleY)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_window_size, 0, 0, 1)
    ZEND_ARG_INFO(0, window)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_window_size_in_pixels, 0, 0, 1)
    ZEND_ARG_INFO(0, window)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_window_pixel_density, 0, 0, 1)
    ZEND_ARG_INFO(0, window)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_window_display_scale, 0, 0, 1)
    ZEND_ARG_INFO(0, window)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_display_content_scale, 0, 0, 1)
    ZEND_ARG_INFO(0, window)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_current_video_driver, 0, 0, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_num_video_drivers, 0, 0, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_video_driver, 0, 0, 1)
    ZEND_ARG_INFO(0, index)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_renderer_output_size, 0, 0, 1)
    ZEND_ARG_INFO(0, renderer)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_start_text_input, 0, 0, 1)
    ZEND_ARG_INFO(0, window)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_stop_text_input, 0, 0, 1)
    ZEND_ARG_INFO(0, window)
ZEND_END_ARG_INFO()

const zend_function_entry sdl3_functions[] = {
    // SDL3 Core
    PHP_FE(sdl_init, arginfo_sdl_init)
    PHP_FE(sdl_quit, arginfo_sdl_quit)
    PHP_FE(sdl_create_window, arginfo_sdl_create_window)
    PHP_FE(sdl_destroy_window, arginfo_sdl_destroy_window)
    PHP_FE(sdl_destroy_renderer, arginfo_sdl_destroy_renderer)
    PHP_FE(sdl_get_window_id, arginfo_sdl_get_window_id)
    PHP_FE(sdl_create_renderer, arginfo_sdl_create_renderer)
    PHP_FE(sdl_get_num_render_drivers, arginfo_sdl_get_num_render_drivers)
    PHP_FE(sdl_get_render_driver, arginfo_sdl_get_render_driver)
    PHP_FE(sdl_set_render_draw_color, arginfo_sdl_set_render_draw_color)
    PHP_FE(sdl_render_clear, arginfo_sdl_render_clear)
    PHP_FE(sdl_render_fill_rect, arginfo_sdl_render_fill_rect)
    PHP_FE(sdl_render_rect, arginfo_sdl_render_rect)
    PHP_FE(sdl_render_present, arginfo_sdl_render_present)
    PHP_FE(sdl_set_render_scale, arginfo_sdl_set_render_scale)
    PHP_FE(sdl_delay, arginfo_sdl_delay)
    PHP_FE(sdl_get_error, arginfo_sdl_get_error)
    PHP_FE(sdl_create_texture_from_surface, arginfo_sdl_create_texture_from_surface)
    PHP_FE(sdl_render_texture, arginfo_sdl_render_texture)
   PHP_FE(sdl_create_texture, arginfo_sdl_create_texture)
    PHP_FE(sdl_destroy_texture, arginfo_sdl_destroy_texture)
    PHP_FE(sdl_update_texture, arginfo_sdl_update_texture)
    PHP_FE(sdl_set_texture_blend_mode, arginfo_sdl_set_texture_blend_mode)
    PHP_FE(sdl_set_texture_alpha_mod, arginfo_sdl_set_texture_alpha_mod)
    // Tray API
    PHP_FE(tray_setup, arginfo_tray_setup)
    PHP_FE(tray_poll, arginfo_tray_poll)
    PHP_FE(tray_exit, arginfo_tray_exit)
    // Desktop notifications
    PHP_FE(desktop_notify, arginfo_desktop_notify)
    PHP_FE(sdl_create_box_shadow_texture, arginfo_sdl_create_box_shadow_texture)
    PHP_FE(sdl_get_render_target, arginfo_sdl_get_render_target)
    PHP_FE(sdl_set_render_target, arginfo_sdl_set_render_target)
    PHP_FE(sdl_rounded_box, arginfo_sdl_rounded_box)
    PHP_FE(sdl_rounded_box_ex, arginfo_sdl_rounded_box_ex)
    PHP_FE(sdl_set_render_clip_rect, arginfo_sdl_set_render_clip_rect)
    PHP_FE(sdl_get_window_size, arginfo_sdl_get_window_size)
    PHP_FE(sdl_get_window_size_in_pixels, arginfo_sdl_get_window_size_in_pixels)
    PHP_FE(sdl_get_window_pixel_density, arginfo_sdl_get_window_pixel_density)
    PHP_FE(sdl_get_window_display_scale, arginfo_sdl_get_window_display_scale)
    PHP_FE(sdl_get_display_content_scale, arginfo_sdl_get_display_content_scale)
    PHP_FE(sdl_get_current_video_driver, arginfo_sdl_get_current_video_driver)
    PHP_FE(sdl_get_num_video_drivers, arginfo_sdl_get_num_video_drivers)
    PHP_FE(sdl_get_video_driver, arginfo_sdl_get_video_driver)
    PHP_FE(sdl_get_renderer_output_size, arginfo_sdl_get_renderer_output_size)
    PHP_FE(sdl_start_text_input, arginfo_sdl_start_text_input)
    PHP_FE(sdl_stop_text_input, arginfo_sdl_stop_text_input)

    // SDL3_image
    PHP_FE(img_load, arginfo_img_load)
    PHP_FE(img_load_texture, arginfo_img_load_texture)

    // SDL3_ttf
    PHP_FE(ttf_init, arginfo_ttf_init)
    PHP_FE(ttf_open_font, arginfo_ttf_open_font)
    PHP_FE(ttf_close_font, arginfo_ttf_close_font)
    PHP_FE(ttf_render_text_solid, arginfo_ttf_render_text_solid)
    PHP_FE(ttf_render_text_blended, arginfo_ttf_render_text_blended)
    PHP_FE(ttf_render_text_shaded, arginfo_ttf_render_text_shaded)
    PHP_FE(ttf_size_text, arginfo_ttf_size_text)

    // SDL3 Events
    PHP_FE(sdl_poll_event, arginfo_sdl_poll_event)
    PHP_FE(sdl_wait_event, arginfo_sdl_wait_event)
    PHP_FE(sdl_wait_event_timeout, arginfo_sdl_wait_event_timeout)
    PHP_FE(sdl_get_mod_state, arginfo_sdl_get_mod_state)
    PHP_FE(sdl_get_clipboard_text, arginfo_sdl_get_clipboard_text)
    PHP_FE(sdl_set_clipboard_text, arginfo_sdl_set_clipboard_text)

    PHP_FE_END
};

zend_module_entry sdl3_module_entry = {
    STANDARD_MODULE_HEADER,
    "sdl3",
    sdl3_functions,
    PHP_MINIT(sdl3),
    NULL, // MSHUTDOWN
    NULL, // RINIT
    NULL, // RSHUTDOWN
    NULL, // MINFO
    PHP_SDL3_VERSION,
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_SDL3
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(sdl3)
#endif
