/*
 * el_gtk4.c — GTK4 backend for the el native widget system (Linux).
 *
 * This file implements the Linux GTK4 widget layer that el_seed.c calls
 * through to when EL_TARGET_LINUX is defined.
 *
 * Architecture:
 *   el program (el code)
 *       → __widget_* C builtins in el_seed.c
 *           → el_gtk4_* C-callable functions declared here
 *               → GtkWidget* / GtkWindow* via GTK4 C API
 *
 * Widget handles: every widget is assigned an int64_t slot index into
 * _el_widgets[]. The el program holds these as opaque Int values.
 * Slot 0 is never valid (reserved; invalid handle = -1 convention).
 *
 * Threading: GTK4 must run on the main thread. el_gtk4_sync_main() uses
 * g_main_context_invoke_full() to dispatch from any thread, waiting for
 * completion via a GMutex/GCond pair — the same role as dispatch_sync() in
 * the AppKit bridge.
 *
 * Callback mechanism: on signal, the bridge calls
 *   dlsym(RTLD_DEFAULT, fn_name)(widget_handle, event_data_string)
 * matching the el_appkit.m / __thread_create pattern in el_seed.c.
 *
 * Compile:
 *   gcc -DEL_TARGET_LINUX $(pkg-config --cflags gtk4) \
 *       el_gtk4.c -c -o el_gtk4.o
 * Then link el_gtk4.o alongside el_seed.c with:
 *   $(pkg-config --libs gtk4) -ldl
 *
 * GTK4 version notes:
 *   • gtk_widget_get_style_context() is deprecated in GTK 4.10+.
 *     A #if GTK_MINOR_VERSION guard selects the correct CSS-provider path.
 *   • gtk_editable_get_text() / gtk_editable_set_text() are the unified
 *     GTK4 text API for GtkEntry and any GtkEditable.
 */

#ifdef EL_TARGET_LINUX

/* _GNU_SOURCE: expose RTLD_DEFAULT (dlfcn.h) and strdup (string.h) on Linux. */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <gtk/gtk.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>

/* G_APPLICATION_DEFAULT_FLAGS was added in GLib 2.74.
 * Ubuntu 22.04 ships GLib 2.72 — use the older alias when running on it. */
#ifndef G_APPLICATION_DEFAULT_FLAGS
#define G_APPLICATION_DEFAULT_FLAGS G_APPLICATION_FLAGS_NONE
#endif

/* el_val_t must already be defined by el_runtime.h / el_seed.h. */
#ifndef EL_VAL_T_DEFINED
typedef int64_t el_val_t;
#endif

#ifndef EL_CSTR
#define EL_CSTR(v)  ((const char*)(uintptr_t)(v))
#endif
#ifndef EL_STR
#define EL_STR(s)   ((el_val_t)(uintptr_t)(s))
#endif

/* ── Widget table ─────────────────────────────────────────────────────────── */

#define EL_GTK4_MAX_WIDGETS 4096

typedef enum {
    EL_WIDGET_FREE      = 0,
    EL_WIDGET_WINDOW    = 1,
    EL_WIDGET_VSTACK    = 2,
    EL_WIDGET_HSTACK    = 3,
    EL_WIDGET_ZSTACK    = 4,
    EL_WIDGET_SCROLL    = 5,
    EL_WIDGET_LABEL     = 6,
    EL_WIDGET_BUTTON    = 7,
    EL_WIDGET_TEXTFIELD = 8,
    EL_WIDGET_TEXTAREA  = 9,
    EL_WIDGET_IMAGE     = 10,
    EL_WIDGET_DIVIDER   = 11,
    EL_WIDGET_SPACER    = 12,
} ElWidgetKind;

typedef struct {
    ElWidgetKind  kind;
    GtkWidget*    widget;   /* Strong reference (g_object_ref'd on alloc). */
    char*         cb_click; /* El function name for click/submit events. */
    char*         cb_change;/* El function name for value-change events. */
} ElWidget;

static ElWidget _el_widgets[EL_GTK4_MAX_WIDGETS];
static int      _el_widget_count = 0;

/* Allocate a slot; returns slot index or -1 if full. Takes a strong ref. */
static int64_t el_widget_alloc(ElWidgetKind kind, GtkWidget* w) {
    for (int i = 1; i < EL_GTK4_MAX_WIDGETS; i++) {
        if (_el_widgets[i].kind == EL_WIDGET_FREE) {
            _el_widgets[i].kind      = kind;
            _el_widgets[i].widget    = (GtkWidget*)g_object_ref(w);
            _el_widgets[i].cb_click  = NULL;
            _el_widgets[i].cb_change = NULL;
            if (i > _el_widget_count) _el_widget_count = i;
            return (int64_t)i;
        }
    }
    return -1;
}

/* Look up a slot. Returns NULL for invalid/free handle. */
static ElWidget* el_widget_get(int64_t handle) {
    if (handle <= 0 || handle >= EL_GTK4_MAX_WIDGETS) return NULL;
    if (_el_widgets[handle].kind == EL_WIDGET_FREE)   return NULL;
    return &_el_widgets[handle];
}

/* Release a slot. Unrefs the widget; callers must remove from parent first. */
static void el_widget_free(int64_t handle) {
    ElWidget* w = el_widget_get(handle);
    if (!w) return;
    if (w->widget) { g_object_unref(w->widget); w->widget = NULL; }
    w->kind = EL_WIDGET_FREE;
    free(w->cb_click);  w->cb_click  = NULL;
    free(w->cb_change); w->cb_change = NULL;
}

/* Forward declaration — defined below in the GtkApplication globals section. */
static int _el_app_running;

/* ── CSS / styling helpers ────────────────────────────────────────────────── */

/*
 * apply_css — attach a CSS snippet to a single widget with APPLICATION priority.
 * GTK 4.10 deprecated gtk_widget_get_style_context(); use gtk_widget_add_css_class()
 * + a display-level provider for new GTK, or the style-context path for older GTK.
 *
 * We use the style-context path behind a version guard because it reliably
 * scopes the CSS to just this widget, which is what the el property setters
 * need (each call replaces/adds to the widget's own stylesheet).
 */
static void apply_css(GtkWidget* widget, const char* css_str) {
    if (!widget || !css_str) return;
    GtkCssProvider* p = gtk_css_provider_new();

#if GTK_CHECK_VERSION(4, 12, 0)
    /* GTK 4.12+: load_from_string takes a plain string (no length). */
    gtk_css_provider_load_from_string(p, css_str);
#else
    gtk_css_provider_load_from_data(p, css_str, -1);
#endif

#if GTK_MINOR_VERSION >= 10
    /* 4.10+: style context is deprecated; use display-scoped provider + css class.
     * We add a unique widget name so the selector targets only this widget. */
    static guint64 _css_id_counter = 0;
    char name_buf[32];
    snprintf(name_buf, sizeof(name_buf), "el-w-%" G_GUINT64_FORMAT, ++_css_id_counter);
    gtk_widget_set_name(widget, name_buf);

    /* Wrap the CSS to scope to this widget's name. */
    char* scoped = NULL;
    int n = asprintf(&scoped, "#%s { %s }", name_buf, css_str);
    (void)n;
    if (scoped) {
        GtkCssProvider* sp = gtk_css_provider_new();
#if GTK_CHECK_VERSION(4, 12, 0)
        gtk_css_provider_load_from_string(sp, scoped);
#else
        gtk_css_provider_load_from_data(sp, scoped, -1);
#endif
        gtk_style_context_add_provider_for_display(
            gtk_widget_get_display(widget),
            GTK_STYLE_PROVIDER(sp),
            GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
        g_object_unref(sp);
        free(scoped);
    }
    g_object_unref(p);
#else
    /* GTK < 4.10: style context is available and widget-scoped. */
    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    gtk_style_context_add_provider(
        gtk_widget_get_style_context(widget),
        GTK_STYLE_PROVIDER(p),
        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    G_GNUC_END_IGNORE_DEPRECATIONS
    g_object_unref(p);
#endif
}

/* ── Main-thread dispatch ────────────────────────────────────────────────── */

/*
 * Dispatch a function on the GTK main thread, blocking until it completes.
 * If already on the main thread, calls the function directly.
 *
 * Use a GMainContext + GMutex/GCond pair so we can synchronously wait for
 * the idle callback to execute, mirroring dispatch_sync(main_queue, ...).
 */

typedef struct {
    void (*fn)(void*);
    void*  data;
    GMutex mutex;
    GCond  cond;
    int    done;
} ElSyncCall;

static gboolean _el_sync_dispatch(gpointer user_data) {
    ElSyncCall* call = (ElSyncCall*)user_data;
    call->fn(call->data);
    g_mutex_lock(&call->mutex);
    call->done = 1;
    g_cond_signal(&call->cond);
    g_mutex_unlock(&call->mutex);
    return G_SOURCE_REMOVE;
}

static void el_gtk4_sync_main(void (*fn)(void*), void* data) {
    /*
     * Pre-run phase (el boot sequence, before native_run_loop):
     *   The GMainContext is not yet iterating. We are on the main thread.
     *   Call directly — no dispatch needed.
     *
     * Post-run phase (callbacks from GTK signal handlers or other threads):
     *   Dispatch to the main context and wait for completion.
     */
    if (!_el_app_running) {
        fn(data);
        return;
    }

    /* Check if we're already on the thread running the default main context. */
    GMainContext* main_ctx = g_main_context_default();
    if (g_main_context_is_owner(main_ctx)) {
        fn(data);
        return;
    }
    ElSyncCall call;
    call.fn   = fn;
    call.data = data;
    call.done = 0;
    g_mutex_init(&call.mutex);
    g_cond_init(&call.cond);

    g_main_context_invoke(main_ctx, _el_sync_dispatch, &call);

    g_mutex_lock(&call.mutex);
    while (!call.done) {
        g_cond_wait(&call.cond, &call.mutex);
    }
    g_mutex_unlock(&call.mutex);
    g_mutex_clear(&call.mutex);
    g_cond_clear(&call.cond);
}

/* Convenience macro: declare a local struct, fill it, dispatch. */
#define EL_SYNC(type, var, ...)                         \
    type var = { __VA_ARGS__ };                         \
    el_gtk4_sync_main(_el_sync_##var, &var)

/* ── El callback invocation ──────────────────────────────────────────────── */

/*
 * El callback functions have signature (per el_seed.c conventions):
 *   void fn_name(int64_t handle, int64_t data)
 * where data is a (const char*) cast to int64_t — same as AppKit bridge.
 */
typedef void (*ElCb2)(int64_t handle, int64_t data);

static void el_gtk4_invoke_cb(const char* fn_name, int64_t handle,
                               const char* data) {
    if (!fn_name || !*fn_name) return;
    void* sym = dlsym(RTLD_DEFAULT, fn_name);
    if (!sym) return;
    ElCb2 fn = (ElCb2)sym;
    fn(handle, (int64_t)(uintptr_t)(data ? data : ""));
}

/* ── GtkApplication (global) ─────────────────────────────────────────────── */

static GtkApplication* _el_app        = NULL;
static int             _el_app_running = 0; /* set to 1 after activate fires */

/* ── Signal callbacks ────────────────────────────────────────────────────── */

/* Slot index stored as widget data so signal handlers can look it up. */
#define EL_SLOT_KEY "el-slot"

static void _el_on_button_clicked(GtkButton* btn, gpointer user_data) {
    (void)btn;
    int64_t slot = (int64_t)(intptr_t)user_data;
    ElWidget* w  = el_widget_get(slot);
    if (w && w->cb_click) {
        el_gtk4_invoke_cb(w->cb_click, slot, "");
    }
}

static void _el_on_entry_changed(GtkEditable* editable, gpointer user_data) {
    int64_t slot = (int64_t)(intptr_t)user_data;
    ElWidget* w  = el_widget_get(slot);
    if (w && w->cb_change) {
        const char* text = gtk_editable_get_text(editable);
        el_gtk4_invoke_cb(w->cb_change, slot, text ? text : "");
    }
}

static void _el_on_entry_activate(GtkEntry* entry, gpointer user_data) {
    (void)entry;
    int64_t slot = (int64_t)(intptr_t)user_data;
    ElWidget* w  = el_widget_get(slot);
    if (w && w->cb_click) {
        const char* text = gtk_editable_get_text(GTK_EDITABLE(entry));
        el_gtk4_invoke_cb(w->cb_click, slot, text ? text : "");
    }
}

/* GtkTextBuffer "changed" signal for textarea. */
static void _el_on_textbuffer_changed(GtkTextBuffer* buf, gpointer user_data) {
    int64_t slot = (int64_t)(intptr_t)user_data;
    ElWidget* w  = el_widget_get(slot);
    if (!w || !w->cb_change) return;
    GtkTextIter start, end;
    gtk_text_buffer_get_bounds(buf, &start, &end);
    char* text = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
    el_gtk4_invoke_cb(w->cb_change, slot, text ? text : "");
    g_free(text);
}

/* ── GtkApplication activate callback ───────────────────────────────────── */

static void _el_app_activate(GtkApplication* app, gpointer user_data) {
    /*
     * Register all pre-created windows with the application, and mark the
     * app as running so subsequent calls use el_gtk4_sync_main dispatching.
     *
     * On macOS the GTK4 Quartz backend requires g_application_run() on the
     * main thread. Windows created before run use gtk_window_new(); we
     * register them here via gtk_application_add_window() so they participate
     * in the application lifecycle (app quits when last window closes, etc.).
     */
    (void)user_data;
    _el_app_running = 1;
    for (int i = 1; i < EL_GTK4_MAX_WIDGETS; i++) {
        if (_el_widgets[i].kind == EL_WIDGET_WINDOW && _el_widgets[i].widget) {
            gtk_application_add_window(app, GTK_WINDOW(_el_widgets[i].widget));
        }
    }
}

/* ── Public C API ─────────────────────────────────────────────────────────── */

/*
 * el_gtk4_init — initialize GTK4 + GtkApplication. Idempotent.
 * Must be called once before any other el_gtk4_* function.
 */
void el_gtk4_init(void) {
    static int done = 0;
    if (done) return;
    done = 1;

    _el_app = gtk_application_new("ai.neuralplatform.el", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(_el_app, "activate", G_CALLBACK(_el_app_activate), NULL);
}

/*
 * el_gtk4_run_loop — start the GTK main loop. Never returns.
 * el code calls __native_run_loop() which dispatches here.
 *
 * On macOS the GTK4 Quartz backend requires g_application_run() to be called
 * from the main thread. Since the el boot sequence (native_init → window_create
 * → widget_build → native_run_loop) runs on the main thread, g_application_run
 * is always called from the main thread here.
 *
 * Windows created before this call use gtk_window_new() (no GApplication
 * needed); the activate handler registers them with the GtkApplication.
 */
void el_gtk4_run_loop(void) {
    if (!_el_app) el_gtk4_init();
    int status = g_application_run(G_APPLICATION(_el_app), 0, NULL);
    (void)status;
}

/* ── Window ───────────────────────────────────────────────────────────────── */

typedef struct {
    const char* title;
    int width, height, min_width, min_height;
    int64_t result;
} _ElWindowCreateArgs;

static void _el_window_create_main(void* vp) {
    _ElWindowCreateArgs* a = (_ElWindowCreateArgs*)vp;
    /*
     * Use gtk_window_new() rather than gtk_application_window_new().
     * gtk_application_window_new() requires the GApplication to be running
     * (i.e., after g_application_run / activate), which is not yet the case
     * when the el boot sequence creates windows before native_run_loop().
     * The activate handler in el_gtk4_run_loop registers these windows with
     * the GtkApplication via gtk_application_add_window().
     */
    GtkWidget* win = gtk_window_new();
    gtk_window_set_title(GTK_WINDOW(win), a->title ? a->title : "");
    gtk_window_set_default_size(GTK_WINDOW(win), a->width, a->height);

    /* Set minimum size via content-area size hint. */
    if (a->min_width > 0 || a->min_height > 0) {
        gtk_widget_set_size_request(win, a->min_width, a->min_height);
    }

    /* Use a vertical GtkBox as the root content widget so children stack
     * vertically by default — mirrors the NSStackView root in AppKit. */
    GtkWidget* root = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_window_set_child(GTK_WINDOW(win), root);

    a->result = el_widget_alloc(EL_WIDGET_WINDOW, win);
}

int64_t el_gtk4_window_create(const char* title, int width, int height,
                               int min_width, int min_height) {
    _ElWindowCreateArgs a = { title, width, height, min_width, min_height, -1 };
    el_gtk4_sync_main(_el_window_create_main, &a);
    return a.result;
}

typedef struct { int64_t handle; } _ElHandleArgs;

static void _el_window_show_main(void* vp) {
    _ElHandleArgs* a = (_ElHandleArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w || w->kind != EL_WIDGET_WINDOW) return;
    gtk_window_present(GTK_WINDOW(w->widget));
}

void el_gtk4_window_show(int64_t handle) {
    _ElHandleArgs a = { handle };
    el_gtk4_sync_main(_el_window_show_main, &a);
}

typedef struct { int64_t handle; const char* title; } _ElSetTitleArgs;

static void _el_window_set_title_main(void* vp) {
    _ElSetTitleArgs* a = (_ElSetTitleArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w || w->kind != EL_WIDGET_WINDOW) return;
    gtk_window_set_title(GTK_WINDOW(w->widget), a->title ? a->title : "");
}

void el_gtk4_window_set_title(int64_t handle, const char* title) {
    _ElSetTitleArgs a = { handle, title };
    el_gtk4_sync_main(_el_window_set_title_main, &a);
}

/* ── Layout containers ───────────────────────────────────────────────────── */

typedef struct {
    GtkOrientation orient;
    int spacing;
    int64_t result;
} _ElStackCreateArgs;

static void _el_stack_create_main(void* vp) {
    _ElStackCreateArgs* a = (_ElStackCreateArgs*)vp;
    GtkWidget* box = gtk_box_new(a->orient, a->spacing);
    ElWidgetKind kind = (a->orient == GTK_ORIENTATION_VERTICAL)
        ? EL_WIDGET_VSTACK : EL_WIDGET_HSTACK;
    a->result = el_widget_alloc(kind, box);
}

int64_t el_gtk4_vstack_create(int spacing) {
    _ElStackCreateArgs a = { GTK_ORIENTATION_VERTICAL, spacing, -1 };
    el_gtk4_sync_main(_el_stack_create_main, &a);
    return a.result;
}

int64_t el_gtk4_hstack_create(int spacing) {
    _ElStackCreateArgs a = { GTK_ORIENTATION_HORIZONTAL, spacing, -1 };
    el_gtk4_sync_main(_el_stack_create_main, &a);
    return a.result;
}

typedef struct { int64_t result; } _ElResultArgs;

static void _el_zstack_create_main(void* vp) {
    _ElResultArgs* a = (_ElResultArgs*)vp;
    GtkWidget* overlay = gtk_overlay_new();
    a->result = el_widget_alloc(EL_WIDGET_ZSTACK, overlay);
}

int64_t el_gtk4_zstack_create(void) {
    _ElResultArgs a = { -1 };
    el_gtk4_sync_main(_el_zstack_create_main, &a);
    return a.result;
}

static void _el_scroll_create_main(void* vp) {
    _ElResultArgs* a = (_ElResultArgs*)vp;
    GtkWidget* sw = gtk_scrolled_window_new();
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    a->result = el_widget_alloc(EL_WIDGET_SCROLL, sw);
}

int64_t el_gtk4_scroll_create(void) {
    _ElResultArgs a = { -1 };
    el_gtk4_sync_main(_el_scroll_create_main, &a);
    return a.result;
}

/* ── Widget factories ─────────────────────────────────────────────────────── */

typedef struct { const char* text; int64_t result; } _ElTextArgs;

static void _el_label_create_main(void* vp) {
    _ElTextArgs* a = (_ElTextArgs*)vp;
    GtkWidget* lbl = gtk_label_new(a->text ? a->text : "");
    gtk_label_set_xalign(GTK_LABEL(lbl), 0.0f);
    gtk_label_set_wrap(GTK_LABEL(lbl), FALSE);
    a->result = el_widget_alloc(EL_WIDGET_LABEL, lbl);
}

int64_t el_gtk4_label_create(const char* text) {
    _ElTextArgs a = { text, -1 };
    el_gtk4_sync_main(_el_label_create_main, &a);
    return a.result;
}

static void _el_button_create_main(void* vp) {
    _ElTextArgs* a = (_ElTextArgs*)vp;
    GtkWidget* btn = gtk_button_new_with_label(a->text ? a->text : "");
    int64_t slot = el_widget_alloc(EL_WIDGET_BUTTON, btn);
    if (slot >= 0) {
        g_signal_connect(btn, "clicked",
            G_CALLBACK(_el_on_button_clicked),
            (gpointer)(intptr_t)slot);
    }
    a->result = slot;
}

int64_t el_gtk4_button_create(const char* label) {
    _ElTextArgs a = { label, -1 };
    el_gtk4_sync_main(_el_button_create_main, &a);
    return a.result;
}

static void _el_text_field_create_main(void* vp) {
    _ElTextArgs* a = (_ElTextArgs*)vp;
    GtkWidget* entry = gtk_entry_new();
    if (a->text && *a->text) {
        gtk_entry_set_placeholder_text(GTK_ENTRY(entry), a->text);
    }
    int64_t slot = el_widget_alloc(EL_WIDGET_TEXTFIELD, entry);
    if (slot >= 0) {
        gpointer slot_ptr = (gpointer)(intptr_t)slot;
        g_signal_connect(entry, "changed",
            G_CALLBACK(_el_on_entry_changed), slot_ptr);
        g_signal_connect(entry, "activate",
            G_CALLBACK(_el_on_entry_activate), slot_ptr);
    }
    a->result = slot;
}

int64_t el_gtk4_text_field_create(const char* placeholder) {
    _ElTextArgs a = { placeholder, -1 };
    el_gtk4_sync_main(_el_text_field_create_main, &a);
    return a.result;
}

static void _el_text_area_create_main(void* vp) {
    _ElTextArgs* a = (_ElTextArgs*)vp;
    GtkWidget* sw = gtk_scrolled_window_new();
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    gtk_widget_set_vexpand(sw, TRUE);

    GtkWidget* tv = gtk_text_view_new();
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(tv), GTK_WRAP_WORD_CHAR);
    gtk_widget_set_hexpand(tv, TRUE);
    gtk_widget_set_vexpand(tv, TRUE);

    /* GTK4 GtkTextView has no native placeholder; set accessible description. */
    if (a->text && *a->text) {
        gtk_accessible_update_property(GTK_ACCESSIBLE(tv),
            GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER, a->text, -1);
    }

    gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw), tv);

    /* Store the scroll window as the widget; text view accessible via child. */
    int64_t slot = el_widget_alloc(EL_WIDGET_TEXTAREA, sw);
    if (slot >= 0) {
        GtkTextBuffer* buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tv));
        g_signal_connect(buf, "changed",
            G_CALLBACK(_el_on_textbuffer_changed),
            (gpointer)(intptr_t)slot);
    }
    a->result = slot;
}

int64_t el_gtk4_text_area_create(const char* placeholder) {
    _ElTextArgs a = { placeholder, -1 };
    el_gtk4_sync_main(_el_text_area_create_main, &a);
    return a.result;
}

static void _el_image_create_main(void* vp) {
    _ElTextArgs* a = (_ElTextArgs*)vp;
    GtkWidget* img = NULL;
    if (a->text && *a->text) {
        /* Try as a filesystem path first. */
        if (g_file_test(a->text, G_FILE_TEST_EXISTS)) {
            img = gtk_picture_new_for_filename(a->text);
        }
        if (!img) {
            /* Fall back to icon name. */
            img = gtk_image_new_from_icon_name(a->text);
        }
    }
    if (!img) {
        img = gtk_picture_new();
    }
    a->result = el_widget_alloc(EL_WIDGET_IMAGE, img);
}

int64_t el_gtk4_image_create(const char* path_or_name) {
    _ElTextArgs a = { path_or_name, -1 };
    el_gtk4_sync_main(_el_image_create_main, &a);
    return a.result;
}

/* ── Widget property setters ─────────────────────────────────────────────── */

typedef struct { int64_t handle; const char* text; } _ElWidgetTextArgs;

static void _el_widget_set_text_main(void* vp) {
    _ElWidgetTextArgs* a = (_ElWidgetTextArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w) return;
    const char* s = a->text ? a->text : "";
    switch (w->kind) {
        case EL_WIDGET_LABEL:
            gtk_label_set_text(GTK_LABEL(w->widget), s);
            break;
        case EL_WIDGET_TEXTFIELD:
            gtk_editable_set_text(GTK_EDITABLE(w->widget), s);
            break;
        case EL_WIDGET_BUTTON:
            gtk_button_set_label(GTK_BUTTON(w->widget), s);
            break;
        case EL_WIDGET_TEXTAREA: {
            GtkWidget* tv = gtk_scrolled_window_get_child(
                GTK_SCROLLED_WINDOW(w->widget));
            if (tv && GTK_IS_TEXT_VIEW(tv)) {
                GtkTextBuffer* buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tv));
                gtk_text_buffer_set_text(buf, s, -1);
            }
            break;
        }
        case EL_WIDGET_WINDOW:
            gtk_window_set_title(GTK_WINDOW(w->widget), s);
            break;
        default: break;
    }
}

void el_gtk4_widget_set_text(int64_t handle, const char* text) {
    _ElWidgetTextArgs a = { handle, text };
    el_gtk4_sync_main(_el_widget_set_text_main, &a);
}

typedef struct { int64_t handle; const char* result; } _ElWidgetGetTextArgs;

static void _el_widget_get_text_main(void* vp) {
    _ElWidgetGetTextArgs* a = (_ElWidgetGetTextArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w) { a->result = ""; return; }
    switch (w->kind) {
        case EL_WIDGET_LABEL:
            a->result = gtk_label_get_text(GTK_LABEL(w->widget));
            break;
        case EL_WIDGET_TEXTFIELD:
            a->result = gtk_editable_get_text(GTK_EDITABLE(w->widget));
            break;
        case EL_WIDGET_BUTTON:
            a->result = gtk_button_get_label(GTK_BUTTON(w->widget));
            break;
        case EL_WIDGET_TEXTAREA: {
            GtkWidget* tv = gtk_scrolled_window_get_child(
                GTK_SCROLLED_WINDOW(w->widget));
            if (tv && GTK_IS_TEXT_VIEW(tv)) {
                GtkTextBuffer* buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tv));
                GtkTextIter start, end;
                gtk_text_buffer_get_bounds(buf, &start, &end);
                /* Returns g_malloc'd string — caller treats it as short-lived. */
                a->result = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
            } else {
                a->result = "";
            }
            break;
        }
        default:
            a->result = "";
            break;
    }
}

const char* el_gtk4_widget_get_text(int64_t handle) {
    _ElWidgetGetTextArgs a = { handle, "" };
    el_gtk4_sync_main(_el_widget_get_text_main, &a);
    return a.result ? strdup(a.result) : strdup("");
}

typedef struct {
    int64_t handle;
    float r, g, b, a;
} _ElColorArgs;

static void _el_widget_set_color_main(void* vp) {
    _ElColorArgs* a = (_ElColorArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w) return;
    char css[128];
    snprintf(css, sizeof(css), "color: rgba(%d,%d,%d,%.3f);",
        (int)(a->r * 255.0f), (int)(a->g * 255.0f),
        (int)(a->b * 255.0f), (double)a->a);
    apply_css(w->widget, css);
}

void el_gtk4_widget_set_color(int64_t handle, float r, float g, float b, float a) {
    _ElColorArgs args = { handle, r, g, b, a };
    el_gtk4_sync_main(_el_widget_set_color_main, &args);
}

static void _el_widget_set_bg_color_main(void* vp) {
    _ElColorArgs* a = (_ElColorArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w || w->kind == EL_WIDGET_WINDOW) return;
    char css[128];
    snprintf(css, sizeof(css), "background-color: rgba(%d,%d,%d,%.3f);",
        (int)(a->r * 255.0f), (int)(a->g * 255.0f),
        (int)(a->b * 255.0f), (double)a->a);
    apply_css(w->widget, css);
}

void el_gtk4_widget_set_bg_color(int64_t handle, float r, float g, float b, float a) {
    _ElColorArgs args = { handle, r, g, b, a };
    el_gtk4_sync_main(_el_widget_set_bg_color_main, &args);
}

typedef struct {
    int64_t handle;
    const char* family;
    int size;
    int bold;
} _ElFontArgs;

static void _el_widget_set_font_main(void* vp) {
    _ElFontArgs* a = (_ElFontArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w) return;
    char css[256];
    const char* fam = (a->family && *a->family && strcmp(a->family, "system") != 0)
        ? a->family : "sans-serif";
    snprintf(css, sizeof(css),
        "font-family: %s; font-size: %dpx; font-weight: %s;",
        fam, a->size, a->bold ? "bold" : "normal");
    apply_css(w->widget, css);
}

void el_gtk4_widget_set_font(int64_t handle, const char* family, int size, int bold) {
    _ElFontArgs a = { handle, family, size, bold };
    el_gtk4_sync_main(_el_widget_set_font_main, &a);
}

typedef struct { int64_t handle; int top, right, bottom, left; } _ElPaddingArgs;

static void _el_widget_set_padding_main(void* vp) {
    _ElPaddingArgs* a = (_ElPaddingArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w) return;
    char css[128];
    snprintf(css, sizeof(css), "padding: %dpx %dpx %dpx %dpx;",
        a->top, a->right, a->bottom, a->left);
    apply_css(w->widget, css);
}

void el_gtk4_widget_set_padding(int64_t handle, int top, int right, int bottom, int left) {
    _ElPaddingArgs a = { handle, top, right, bottom, left };
    el_gtk4_sync_main(_el_widget_set_padding_main, &a);
}

typedef struct { int64_t handle; int value; } _ElIntPropArgs;

static void _el_widget_set_width_main(void* vp) {
    _ElIntPropArgs* a = (_ElIntPropArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w || w->kind == EL_WIDGET_WINDOW) return;
    gint cur_h = -1;
    gtk_widget_get_size_request(w->widget, NULL, &cur_h);
    gtk_widget_set_size_request(w->widget, a->value, cur_h);
}

void el_gtk4_widget_set_width(int64_t handle, int width) {
    _ElIntPropArgs a = { handle, width };
    el_gtk4_sync_main(_el_widget_set_width_main, &a);
}

static void _el_widget_set_height_main(void* vp) {
    _ElIntPropArgs* a = (_ElIntPropArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w || w->kind == EL_WIDGET_WINDOW) return;
    gint cur_w = -1;
    gtk_widget_get_size_request(w->widget, &cur_w, NULL);
    gtk_widget_set_size_request(w->widget, cur_w, a->value);
}

void el_gtk4_widget_set_height(int64_t handle, int height) {
    _ElIntPropArgs a = { handle, height };
    el_gtk4_sync_main(_el_widget_set_height_main, &a);
}

static void _el_widget_set_flex_main(void* vp) {
    _ElIntPropArgs* a = (_ElIntPropArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w || w->kind == EL_WIDGET_WINDOW) return;
    gboolean expand = (a->value > 0) ? TRUE : FALSE;
    gtk_widget_set_hexpand(w->widget, expand);
    gtk_widget_set_vexpand(w->widget, expand);
}

void el_gtk4_widget_set_flex(int64_t handle, int flex) {
    _ElIntPropArgs a = { handle, flex };
    el_gtk4_sync_main(_el_widget_set_flex_main, &a);
}

static void _el_widget_set_corner_radius_main(void* vp) {
    _ElIntPropArgs* a = (_ElIntPropArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w || w->kind == EL_WIDGET_WINDOW) return;
    char css[64];
    snprintf(css, sizeof(css), "border-radius: %dpx;", a->value);
    apply_css(w->widget, css);
}

void el_gtk4_widget_set_corner_radius(int64_t handle, int radius) {
    _ElIntPropArgs a = { handle, radius };
    el_gtk4_sync_main(_el_widget_set_corner_radius_main, &a);
}

static void _el_widget_set_disabled_main(void* vp) {
    _ElIntPropArgs* a = (_ElIntPropArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w) return;
    gtk_widget_set_sensitive(w->widget, !a->value);
}

void el_gtk4_widget_set_disabled(int64_t handle, int disabled) {
    _ElIntPropArgs a = { handle, disabled };
    el_gtk4_sync_main(_el_widget_set_disabled_main, &a);
}

static void _el_widget_set_hidden_main(void* vp) {
    _ElIntPropArgs* a = (_ElIntPropArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w || w->kind == EL_WIDGET_WINDOW) return;
    gtk_widget_set_visible(w->widget, !a->value);
}

void el_gtk4_widget_set_hidden(int64_t handle, int hidden) {
    _ElIntPropArgs a = { handle, hidden };
    el_gtk4_sync_main(_el_widget_set_hidden_main, &a);
}

/* ── Child management ─────────────────────────────────────────────────────── */

typedef struct { int64_t parent; int64_t child; } _ElParentChildArgs;

static void _el_widget_add_child_main(void* vp) {
    _ElParentChildArgs* a = (_ElParentChildArgs*)vp;
    ElWidget* pw = el_widget_get(a->parent);
    ElWidget* cw = el_widget_get(a->child);
    if (!pw || !cw) return;
    if (cw->kind == EL_WIDGET_WINDOW) return; /* cannot add a window as child */

    GtkWidget* child_widget = cw->widget;

    switch (pw->kind) {
        case EL_WIDGET_WINDOW: {
            /* Append to the root VBox that window_create installs as child. */
            GtkWidget* root = gtk_window_get_child(GTK_WINDOW(pw->widget));
            if (root && GTK_IS_BOX(root)) {
                gtk_box_append(GTK_BOX(root), child_widget);
            } else {
                /* Fallback: replace root child. */
                gtk_window_set_child(GTK_WINDOW(pw->widget), child_widget);
            }
            break;
        }
        case EL_WIDGET_VSTACK:
        case EL_WIDGET_HSTACK:
            gtk_box_append(GTK_BOX(pw->widget), child_widget);
            break;
        case EL_WIDGET_ZSTACK:
            /* First child becomes the base; subsequent children are overlays. */
            if (!gtk_overlay_get_child(GTK_OVERLAY(pw->widget))) {
                gtk_overlay_set_child(GTK_OVERLAY(pw->widget), child_widget);
            } else {
                gtk_overlay_add_overlay(GTK_OVERLAY(pw->widget), child_widget);
            }
            break;
        case EL_WIDGET_SCROLL:
            gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(pw->widget),
                child_widget);
            break;
        default:
            /* Generic fallback — shouldn't be reached for standard widget kinds. */
            break;
    }
}

void el_gtk4_widget_add_child(int64_t parent, int64_t child) {
    _ElParentChildArgs a = { parent, child };
    el_gtk4_sync_main(_el_widget_add_child_main, &a);
}

static void _el_widget_remove_child_main(void* vp) {
    _ElParentChildArgs* a = (_ElParentChildArgs*)vp;
    ElWidget* pw = el_widget_get(a->parent);
    ElWidget* cw = el_widget_get(a->child);
    if (!pw || !cw) return;
    if (cw->kind == EL_WIDGET_WINDOW) return;

    GtkWidget* child_widget = cw->widget;

    switch (pw->kind) {
        case EL_WIDGET_WINDOW: {
            GtkWidget* root = gtk_window_get_child(GTK_WINDOW(pw->widget));
            if (root && GTK_IS_BOX(root)) {
                gtk_box_remove(GTK_BOX(root), child_widget);
            }
            break;
        }
        case EL_WIDGET_VSTACK:
        case EL_WIDGET_HSTACK:
            gtk_box_remove(GTK_BOX(pw->widget), child_widget);
            break;
        case EL_WIDGET_ZSTACK:
            gtk_overlay_remove_overlay(GTK_OVERLAY(pw->widget), child_widget);
            break;
        case EL_WIDGET_SCROLL:
            gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(pw->widget), NULL);
            break;
        default:
            break;
    }
}

void el_gtk4_widget_remove_child(int64_t parent, int64_t child) {
    _ElParentChildArgs a = { parent, child };
    el_gtk4_sync_main(_el_widget_remove_child_main, &a);
}

/* ── Event registration ───────────────────────────────────────────────────── */

/*
 * on_click / on_change / on_submit just store the function name string.
 * The actual g_signal_connect calls happen at widget creation time.
 * Here we update the stored callback so the already-connected signal handler
 * will call the new function name.
 */

void el_gtk4_widget_on_click(int64_t handle, const char* fn_name) {
    ElWidget* w = el_widget_get(handle);
    if (!w) return;
    free(w->cb_click);
    w->cb_click = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
}

void el_gtk4_widget_on_change(int64_t handle, const char* fn_name) {
    ElWidget* w = el_widget_get(handle);
    if (!w) return;
    free(w->cb_change);
    w->cb_change = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
}

void el_gtk4_widget_on_submit(int64_t handle, const char* fn_name) {
    /* For text fields, "submit" = Enter key activate → stored in cb_click. */
    el_gtk4_widget_on_click(handle, fn_name);
}

/* ── Widget destroy ───────────────────────────────────────────────────────── */

static void _el_widget_destroy_main(void* vp) {
    _ElHandleArgs* a = (_ElHandleArgs*)vp;
    ElWidget* w = el_widget_get(a->handle);
    if (!w) return;
    if (w->kind == EL_WIDGET_WINDOW) {
        gtk_window_destroy(GTK_WINDOW(w->widget));
    } else {
        /* Unparent the widget before freeing the slot. */
        GtkWidget* parent = gtk_widget_get_parent(w->widget);
        if (parent) {
            if (GTK_IS_BOX(parent)) {
                gtk_box_remove(GTK_BOX(parent), w->widget);
            } else if (GTK_IS_OVERLAY(parent)) {
                gtk_overlay_remove_overlay(GTK_OVERLAY(parent), w->widget);
            } else if (GTK_IS_SCROLLED_WINDOW(parent)) {
                gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(parent), NULL);
            }
            /* gtk_window_get_child case: the parent is a GtkBox inside a window */
        }
    }
    el_widget_free(a->handle);
}

void el_gtk4_widget_destroy(int64_t handle) {
    _ElHandleArgs a = { handle };
    el_gtk4_sync_main(_el_widget_destroy_main, &a);
}

#endif /* EL_TARGET_LINUX */
