/*
 * el_uikit.m — UIKit backend for the el native widget system.
 *
 * This file implements the iOS UIKit widget layer that el_seed.c calls
 * through to when EL_TARGET_IOS is defined.
 *
 * Architecture:
 *   el program (el code)
 *       → __widget_* C builtins in el_seed.c
 *           → el_uikit_* C-callable functions declared here
 *               → UIView/UIWindow/UIStackView/UIButton/etc. via ObjC
 *
 * Widget handles: every widget (window, view, control) 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 as null handle = -1 convention).
 *
 * Threading: All UIKit calls MUST run on the main thread. el_uikit_sync_main
 * bounces work to the main thread synchronously if called from a background thread.
 *
 * Callback mechanism: when a widget fires an event (button tap, text change),
 * the bridge looks up the registered callback function name, then calls
 *   dlsym(RTLD_DEFAULT, fn_name)(widget_handle, event_data_string)
 * This resolves the El function symbol at runtime, matching the __thread_create
 * pattern already established in el_seed.c.
 *
 * iOS lifecycle: UIApplicationMain never returns. The el program's "main" body
 * (window creation, layout, show) must execute inside applicationDidFinishLaunchingWithOptions.
 * The caller sets el_main_entry_fn before calling __native_run_loop so the
 * delegate can invoke it at the right time.
 *
 * Compile (MRC — no ARC; manual retain/release needed for struct-embedded id):
 *   clang -std=gnu11 -ObjC -fno-objc-arc -DEL_TARGET_IOS -framework UIKit \
 *         -c el_uikit.m -o el_uikit.o
 * Then link el_uikit.o alongside el_seed.c.
 *
 * Do NOT use -fobjc-arc: the widget table stores id in a plain C struct. ARC
 * forbids explicit retain/release on struct-embedded object pointers and cannot
 * insert automatic retain/release through C struct boundaries. MRC is the
 * correct choice for this kind of C/ObjC bridge.
 *
 * Link flags required:
 *   -framework UIKit
 */

#ifdef EL_TARGET_IOS

#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <dlfcn.h>
#include "el_runtime.h"

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

#define EL_UIKIT_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;
    id            obj;        /* UIWindow* or UIView* subclass, retained */
    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_UIKIT_MAX_WIDGETS];
static int      _el_widget_count = 0;

/* Side table for ObjC delegate objects keyed by widget slot (MRC retained). */
static id _el_delegates[EL_UIKIT_MAX_WIDGETS];

/* Allocate a slot and return its index. Returns -1 if full. */
static int64_t el_widget_alloc(ElWidgetKind kind, id obj) {
    for (int i = 1; i < EL_UIKIT_MAX_WIDGETS; i++) {
        if (_el_widgets[i].kind == EL_WIDGET_FREE) {
            _el_widgets[i].kind      = kind;
            _el_widgets[i].obj       = [obj retain];
            _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;
}

static ElWidget* el_widget_get(int64_t handle) {
    if (handle <= 0 || handle >= EL_UIKIT_MAX_WIDGETS) return NULL;
    if (_el_widgets[handle].kind == EL_WIDGET_FREE)     return NULL;
    return &_el_widgets[handle];
}

static void el_widget_free(int64_t handle) {
    ElWidget* w = el_widget_get(handle);
    if (!w) return;
    [w->obj release];
    w->obj  = nil;
    w->kind = EL_WIDGET_FREE;
    free(w->cb_click);  w->cb_click  = NULL;
    free(w->cb_change); w->cb_change = NULL;
}

/* ── Dispatch helpers ─────────────────────────────────────────────────────── */

/* Run a block on the main thread, waiting for completion. Safe to call from
 * any thread including the main thread. Uses dispatch_sync so callers can
 * rely on the block having run before the function returns. */
static void el_uikit_sync_main(void (^block)(void)) {
    if ([NSThread isMainThread]) {
        block();
    } else {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

/* ── El callback invocation ──────────────────────────────────────────────── */
/*
 * Invoke an El callback by symbol name. The El function must have the
 * signature:  fn handler(handle: Int, data: String) -> Void
 * which compiles to:  void fn_name(el_val_t handle, el_val_t data)
 */
typedef void (*ElCb2)(int64_t handle, int64_t data);

static void el_uikit_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 : ""));
}

/* ── ObjC helper: ElEventProxy — button tap and text event target ─────────── */

/*
 * ElEventProxy is a lightweight ObjC action target / delegate aggregator.
 * One instance is allocated per widget that has events. Stored in _el_delegates[].
 */
@interface ElEventProxy : NSObject <UITextFieldDelegate>
@property (nonatomic, assign) int64_t widgetHandle;
/* UIButton tap */
- (void)elButtonTapped:(id)sender;
/* UITextField UIControlEventEditingChanged */
- (void)elTextChanged:(id)sender;
@end

@implementation ElEventProxy

- (void)elButtonTapped:(id)sender {
    (void)sender;
    ElWidget* w = el_widget_get(self.widgetHandle);
    if (w && w->cb_click) {
        el_uikit_invoke_cb(w->cb_click, self.widgetHandle, "");
    }
}

- (void)elTextChanged:(id)sender {
    UITextField* tf = (UITextField*)sender;
    ElWidget* w = el_widget_get(self.widgetHandle);
    if (!w) return;
    const char* val = [tf.text UTF8String];
    if (w->cb_change) {
        el_uikit_invoke_cb(w->cb_change, self.widgetHandle, val ? val : "");
    }
}

/* UITextFieldDelegate — return key fires on_submit (stored in cb_click). */
- (BOOL)textFieldShouldReturn:(UITextField*)textField {
    ElWidget* w = el_widget_get(self.widgetHandle);
    if (w && w->cb_click) {
        const char* val = [textField.text UTF8String];
        el_uikit_invoke_cb(w->cb_click, self.widgetHandle, val ? val : "");
    }
    [textField resignFirstResponder];
    return YES;
}

@end

/* ── UITextView change delegate ──────────────────────────────────────────── */

@interface ElTextViewDelegate : NSObject <UITextViewDelegate>
@property (nonatomic, assign) int64_t widgetHandle;
@end

@implementation ElTextViewDelegate

- (void)textViewDidChange:(UITextView*)textView {
    ElWidget* w = el_widget_get(self.widgetHandle);
    if (!w || !w->cb_change) return;
    const char* val = [textView.text UTF8String];
    el_uikit_invoke_cb(w->cb_change, self.widgetHandle, val ? val : "");
}

@end

/* ── iOS application lifecycle ───────────────────────────────────────────── */

/*
 * el_main_entry_fn — set by el's main() (via __native_run_loop) before calling
 * UIApplicationMain. The app delegate invokes it in didFinishLaunching so that
 * the el program's window/widget setup runs at the correct lifecycle point.
 */
void (*el_main_entry_fn)(void) = NULL;

/* argc/argv forwarded from el_runtime_init_args (or main). */
static int    _el_argc = 0;
static char** _el_argv = NULL;

/*
 * el_uikit_set_args — call from el_runtime_init_args / main() before
 * __native_run_loop to forward argc/argv to UIApplicationMain.
 */
void el_uikit_set_args(int argc, char** argv) {
    _el_argc = argc;
    _el_argv = argv;
}

@interface ElAppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) UIWindow *keyWindow;
@end

@implementation ElAppDelegate

- (BOOL)application:(UIApplication*)app
    didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
    (void)app; (void)launchOptions;
    /*
     * The el program called __native_init and set up el_main_entry_fn before
     * UIApplicationMain was entered. We call it here so window creation and
     * widget layout happen on the main thread at the right lifecycle moment.
     */
    if (el_main_entry_fn) {
        el_main_entry_fn();
    }
    return YES;
}

- (void)applicationWillResignActive:(UIApplication*)app { (void)app; }
- (void)applicationDidEnterBackground:(UIApplication*)app { (void)app; }
- (void)applicationWillEnterForeground:(UIApplication*)app { (void)app; }
- (void)applicationDidBecomeActive:(UIApplication*)app { (void)app; }

@end

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

/*
 * el_uikit_init — one-time initialisation. Idempotent. Call before any other
 * el_uikit_* function. On iOS there is no UIApplication equivalent of
 * [NSApplication sharedApplication] to call here — the real initialisation
 * happens inside UIApplicationMain which is entered from el_uikit_run_loop.
 * This function records that init was requested.
 */
static int _el_uikit_init_done = 0;

void el_uikit_init(void) {
    _el_uikit_init_done = 1;
}

/*
 * el_uikit_run_loop — enters UIApplicationMain. Never returns.
 * Must be called from the process main thread as the final step of main().
 * Sets el_main_entry_fn to the provided function pointer before entering
 * UIApplicationMain so the app delegate can call back into el code.
 */
void el_uikit_run_loop(void (*entry_fn)(void)) {
    el_main_entry_fn = entry_fn;
    UIApplicationMain(_el_argc, _el_argv, nil, @"ElAppDelegate");
}

/*
 * el_uikit_window_create — create a UIWindow and configure a root
 * UINavigationController + UIViewController. Returns window handle.
 *
 * On iOS there is exactly one full-screen window. width/height from the
 * manifest are stored but the window always fills the screen. The title
 * is applied to the root view controller's navigationItem.
 */
int64_t el_uikit_window_create(const char* title, int width, int height,
                                int min_width, int min_height) {
    (void)width; (void)height; (void)min_width; (void)min_height;
    __block int64_t handle = -1;
    el_uikit_sync_main(^{
        UIScreen* screen = [UIScreen mainScreen];
        UIWindow* win = [[UIWindow alloc] initWithFrame:[screen bounds]];
        win.backgroundColor = [UIColor systemBackgroundColor];

        /* Root view controller with a vertical UIStackView as content. */
        UIViewController* rootVC = [[UIViewController alloc] init];
        NSString* titleStr = [NSString stringWithUTF8String:title ? title : ""];
        rootVC.title = titleStr;

        UIStackView* rootStack = [[UIStackView alloc] initWithFrame:CGRectZero];
        rootStack.axis = UILayoutConstraintAxisVertical;
        rootStack.alignment = UIStackViewAlignmentFill;
        rootStack.distribution = UIStackViewDistributionFill;
        rootStack.spacing = 0.0;
        rootStack.translatesAutoresizingMaskIntoConstraints = NO;
        [rootVC.view addSubview:rootStack];

        /* Pin root stack to safe area. */
        UILayoutGuide* safe = rootVC.view.safeAreaLayoutGuide;
        [NSLayoutConstraint activateConstraints:@[
            [rootStack.topAnchor constraintEqualToAnchor:safe.topAnchor],
            [rootStack.leadingAnchor constraintEqualToAnchor:safe.leadingAnchor],
            [rootStack.trailingAnchor constraintEqualToAnchor:safe.trailingAnchor],
            [rootStack.bottomAnchor constraintEqualToAnchor:safe.bottomAnchor],
        ]];

        UINavigationController* navCtrl =
            [[UINavigationController alloc] initWithRootViewController:rootVC];
        win.rootViewController = navCtrl;

        /* Store the window. The root stack is accessible via
         *   ((UINavigationController*)win.rootViewController)
         *     .topViewController.view.subviews[0]
         * but we reach it by casting obj to UIWindow* and digging at add_child time. */
        handle = el_widget_alloc(EL_WIDGET_WINDOW, win);
        [win release];
        [rootVC release];
        [rootStack release];
        [navCtrl release];
    });
    return handle;
}

/*
 * el_uikit_window_show — make the window key and visible.
 */
void el_uikit_window_show(int64_t handle) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w || w->kind != EL_WIDGET_WINDOW) return;
        UIWindow* win = (UIWindow*)w->obj;
        [win makeKeyAndVisible];
    });
}

/*
 * el_uikit_window_set_title — update nav bar title at runtime.
 */
void el_uikit_window_set_title(int64_t handle, const char* title) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w || w->kind != EL_WIDGET_WINDOW) return;
        UIWindow* win = (UIWindow*)w->obj;
        UINavigationController* nav = (UINavigationController*)win.rootViewController;
        if ([nav isKindOfClass:[UINavigationController class]]) {
            nav.topViewController.title =
                [NSString stringWithUTF8String:title ? title : ""];
        }
    });
}

/* ── Layout container factories ───────────────────────────────────────────── */

static int64_t make_uistack(UILayoutConstraintAxis axis, CGFloat spacing) {
    __block int64_t handle = -1;
    el_uikit_sync_main(^{
        UIStackView* sv = [[UIStackView alloc] initWithFrame:CGRectZero];
        sv.axis = axis;
        sv.alignment = UIStackViewAlignmentFill;
        sv.distribution = UIStackViewDistributionFill;
        sv.spacing = spacing;
        sv.translatesAutoresizingMaskIntoConstraints = NO;
        ElWidgetKind kind = (axis == UILayoutConstraintAxisVertical)
            ? EL_WIDGET_VSTACK : EL_WIDGET_HSTACK;
        handle = el_widget_alloc(kind, sv);
        [sv release];
    });
    return handle;
}

int64_t el_uikit_vstack_create(int spacing) {
    return make_uistack(UILayoutConstraintAxisVertical, (CGFloat)spacing);
}

int64_t el_uikit_hstack_create(int spacing) {
    return make_uistack(UILayoutConstraintAxisHorizontal, (CGFloat)spacing);
}

int64_t el_uikit_zstack_create(void) {
    /* ZStack = plain UIView; children are added with addSubview and use
     * Auto Layout or absolute frames set by the caller. */
    __block int64_t handle = -1;
    el_uikit_sync_main(^{
        UIView* v = [[UIView alloc] initWithFrame:CGRectZero];
        v.translatesAutoresizingMaskIntoConstraints = NO;
        handle = el_widget_alloc(EL_WIDGET_ZSTACK, v);
        [v release];
    });
    return handle;
}

int64_t el_uikit_scroll_create(void) {
    __block int64_t handle = -1;
    el_uikit_sync_main(^{
        UIScrollView* sv = [[UIScrollView alloc] initWithFrame:CGRectZero];
        sv.translatesAutoresizingMaskIntoConstraints = NO;
        sv.alwaysBounceVertical = YES;
        sv.showsHorizontalScrollIndicator = NO;
        handle = el_widget_alloc(EL_WIDGET_SCROLL, sv);
        [sv release];
    });
    return handle;
}

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

int64_t el_uikit_label_create(const char* text) {
    __block int64_t handle = -1;
    el_uikit_sync_main(^{
        UILabel* lbl = [[UILabel alloc] initWithFrame:CGRectZero];
        lbl.text = [NSString stringWithUTF8String:text ? text : ""];
        lbl.numberOfLines = 0;
        lbl.translatesAutoresizingMaskIntoConstraints = NO;
        handle = el_widget_alloc(EL_WIDGET_LABEL, lbl);
        [lbl release];
    });
    return handle;
}

int64_t el_uikit_button_create(const char* label) {
    __block int64_t handle = -1;
    el_uikit_sync_main(^{
        UIButton* btn = [UIButton buttonWithType:UIButtonTypeSystem];
        [btn setTitle:[NSString stringWithUTF8String:label ? label : ""]
             forState:UIControlStateNormal];
        btn.translatesAutoresizingMaskIntoConstraints = NO;

        ElEventProxy* proxy = [[ElEventProxy alloc] init];
        [btn addTarget:proxy
                action:@selector(elButtonTapped:)
      forControlEvents:UIControlEventTouchUpInside];

        int64_t h = el_widget_alloc(EL_WIDGET_BUTTON, btn);
        proxy.widgetHandle = h;
        _el_delegates[h] = proxy; /* retain in side table */
        handle = h;
        /* btn is autoreleased from buttonWithType: — no release needed */
    });
    return handle;
}

int64_t el_uikit_text_field_create(const char* placeholder) {
    __block int64_t handle = -1;
    el_uikit_sync_main(^{
        UITextField* tf = [[UITextField alloc] initWithFrame:CGRectZero];
        tf.translatesAutoresizingMaskIntoConstraints = NO;
        if (placeholder && *placeholder) {
            tf.placeholder = [NSString stringWithUTF8String:placeholder];
        }
        tf.borderStyle = UITextBorderStyleRoundedRect;
        tf.autocorrectionType = UITextAutocorrectionTypeNo;

        ElEventProxy* proxy = [[ElEventProxy alloc] init];
        tf.delegate = proxy;
        [tf addTarget:proxy
               action:@selector(elTextChanged:)
     forControlEvents:UIControlEventEditingChanged];

        int64_t h = el_widget_alloc(EL_WIDGET_TEXTFIELD, tf);
        proxy.widgetHandle = h;
        _el_delegates[h] = proxy;
        handle = h;
        [tf release];
    });
    return handle;
}

int64_t el_uikit_text_area_create(const char* placeholder) {
    __block int64_t handle = -1;
    el_uikit_sync_main(^{
        UITextView* tv = [[UITextView alloc] initWithFrame:CGRectZero];
        tv.translatesAutoresizingMaskIntoConstraints = NO;
        tv.font = [UIFont systemFontOfSize:14.0];
        tv.layer.borderColor = [[UIColor systemGray4Color] CGColor];
        tv.layer.borderWidth = 1.0;
        tv.layer.cornerRadius = 6.0;
        tv.clipsToBounds = YES;
        /* UITextView has no native placeholder; set accessibility hint. */
        if (placeholder && *placeholder) {
            tv.accessibilityHint = [NSString stringWithUTF8String:placeholder];
        }

        ElTextViewDelegate* del = [[ElTextViewDelegate alloc] init];
        tv.delegate = del;

        int64_t h = el_widget_alloc(EL_WIDGET_TEXTAREA, tv);
        del.widgetHandle = h;
        _el_delegates[h] = del;
        handle = h;
        [tv release];
    });
    return handle;
}

int64_t el_uikit_image_create(const char* path_or_name) {
    __block int64_t handle = -1;
    el_uikit_sync_main(^{
        UIImage* img = nil;
        if (path_or_name && *path_or_name) {
            /* Try filesystem path first, then bundle asset name. */
            img = [UIImage imageWithContentsOfFile:
                [NSString stringWithUTF8String:path_or_name]];
            if (!img) {
                img = [UIImage imageNamed:
                    [NSString stringWithUTF8String:path_or_name]];
            }
        }
        UIImageView* iv = [[UIImageView alloc] initWithFrame:CGRectZero];
        iv.translatesAutoresizingMaskIntoConstraints = NO;
        iv.contentMode = UIViewContentModeScaleAspectFit;
        if (img) {
            iv.image = img;
        }
        handle = el_widget_alloc(EL_WIDGET_IMAGE, iv);
        [iv release];
    });
    return handle;
}

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

void el_uikit_widget_set_text(int64_t handle, const char* text) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w) return;
        NSString* s = [NSString stringWithUTF8String:text ? text : ""];
        switch (w->kind) {
            case EL_WIDGET_LABEL:
                ((UILabel*)w->obj).text = s;
                break;
            case EL_WIDGET_TEXTFIELD:
                ((UITextField*)w->obj).text = s;
                break;
            case EL_WIDGET_BUTTON:
                [(UIButton*)w->obj setTitle:s forState:UIControlStateNormal];
                break;
            case EL_WIDGET_TEXTAREA:
                ((UITextView*)w->obj).text = s;
                break;
            case EL_WIDGET_WINDOW:
                el_uikit_window_set_title(handle, text);
                break;
            default: break;
        }
    });
}

const char* el_uikit_widget_get_text(int64_t handle) {
    __block const char* result = "";
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w) return;
        NSString* s = nil;
        switch (w->kind) {
            case EL_WIDGET_LABEL:
                s = ((UILabel*)w->obj).text;
                break;
            case EL_WIDGET_TEXTFIELD:
                s = ((UITextField*)w->obj).text;
                break;
            case EL_WIDGET_BUTTON:
                s = [(UIButton*)w->obj titleForState:UIControlStateNormal];
                break;
            case EL_WIDGET_TEXTAREA:
                s = ((UITextView*)w->obj).text;
                break;
            default: break;
        }
        if (s) result = strdup([s UTF8String]);
    });
    return result;
}

void el_uikit_widget_set_color(int64_t handle, float r, float g, float b, float a) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w) return;
        UIColor* c = [UIColor colorWithRed:(CGFloat)(r / 255.0)
                                     green:(CGFloat)(g / 255.0)
                                      blue:(CGFloat)(b / 255.0)
                                     alpha:(CGFloat)(a / 255.0)];
        switch (w->kind) {
            case EL_WIDGET_LABEL:
                ((UILabel*)w->obj).textColor = c;
                break;
            case EL_WIDGET_TEXTFIELD:
                ((UITextField*)w->obj).textColor = c;
                break;
            case EL_WIDGET_TEXTAREA:
                ((UITextView*)w->obj).textColor = c;
                break;
            case EL_WIDGET_BUTTON:
                [(UIButton*)w->obj setTitleColor:c forState:UIControlStateNormal];
                ((UIButton*)w->obj).tintColor = c;
                break;
            case EL_WIDGET_VSTACK:
            case EL_WIDGET_HSTACK:
            case EL_WIDGET_ZSTACK:
                ((UIView*)w->obj).backgroundColor = c;
                break;
            default: break;
        }
    });
}

void el_uikit_widget_set_bg_color(int64_t handle, float r, float g, float b, float a) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w) return;
        if (w->kind == EL_WIDGET_WINDOW) {
            UIWindow* win = (UIWindow*)w->obj;
            win.backgroundColor = [UIColor colorWithRed:(CGFloat)(r / 255.0)
                                                  green:(CGFloat)(g / 255.0)
                                                   blue:(CGFloat)(b / 255.0)
                                                  alpha:(CGFloat)(a / 255.0)];
            return;
        }
        UIView* v = (UIView*)w->obj;
        v.backgroundColor = [UIColor colorWithRed:(CGFloat)(r / 255.0)
                                            green:(CGFloat)(g / 255.0)
                                             blue:(CGFloat)(b / 255.0)
                                            alpha:(CGFloat)(a / 255.0)];
    });
}

void el_uikit_widget_set_font(int64_t handle, const char* family, int size, int bold) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w) return;
        UIFont* font;
        if (bold) {
            font = [UIFont boldSystemFontOfSize:(CGFloat)size];
        } else if (family && *family && strcmp(family, "system") != 0) {
            font = [UIFont fontWithName:[NSString stringWithUTF8String:family]
                                   size:(CGFloat)size];
            if (!font) font = [UIFont systemFontOfSize:(CGFloat)size];
        } else {
            font = [UIFont systemFontOfSize:(CGFloat)size];
        }
        switch (w->kind) {
            case EL_WIDGET_LABEL:
                ((UILabel*)w->obj).font = font;
                break;
            case EL_WIDGET_TEXTFIELD:
                ((UITextField*)w->obj).font = font;
                break;
            case EL_WIDGET_TEXTAREA:
                ((UITextView*)w->obj).font = font;
                break;
            case EL_WIDGET_BUTTON:
                ((UIButton*)w->obj).titleLabel.font = font;
                break;
            default: break;
        }
    });
}

void el_uikit_widget_set_padding(int64_t handle, int top, int right, int bottom, int left) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w) return;
        UIEdgeInsets insets = UIEdgeInsetsMake(
            (CGFloat)top, (CGFloat)left, (CGFloat)bottom, (CGFloat)right);
        if (w->kind == EL_WIDGET_VSTACK || w->kind == EL_WIDGET_HSTACK) {
            UIStackView* sv = (UIStackView*)w->obj;
            sv.layoutMargins = insets;
            sv.layoutMarginsRelativeArrangement = YES;
        } else if (w->kind == EL_WIDGET_TEXTAREA) {
            UITextView* tv = (UITextView*)w->obj;
            tv.textContainerInset = insets;
        } else {
            UIView* v = (UIView*)w->obj;
            v.layoutMargins = insets;
        }
    });
}

void el_uikit_widget_set_width(int64_t handle, int width) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w || w->kind == EL_WIDGET_WINDOW) return;
        UIView* v = (UIView*)w->obj;
        NSLayoutConstraint* c = [v.widthAnchor
            constraintEqualToConstant:(CGFloat)width];
        c.priority = UILayoutPriorityDefaultHigh;
        c.active = YES;
    });
}

void el_uikit_widget_set_height(int64_t handle, int height) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w || w->kind == EL_WIDGET_WINDOW) return;
        UIView* v = (UIView*)w->obj;
        NSLayoutConstraint* c = [v.heightAnchor
            constraintEqualToConstant:(CGFloat)height];
        c.priority = UILayoutPriorityDefaultHigh;
        c.active = YES;
    });
}

void el_uikit_widget_set_flex(int64_t handle, int flex) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w || w->kind == EL_WIDGET_WINDOW) return;
        UIView* v = (UIView*)w->obj;
        /* flex > 0 → low hugging priority so the view expands to fill space.
         * flex == 0 → high hugging priority so the view stays at intrinsic size. */
        UILayoutPriority p = (flex > 0)
            ? UILayoutPriorityDefaultLow
            : UILayoutPriorityDefaultHigh;
        [v setContentHuggingPriority:p
                              forAxis:UILayoutConstraintAxisHorizontal];
        [v setContentHuggingPriority:p
                              forAxis:UILayoutConstraintAxisVertical];
    });
}

void el_uikit_widget_set_corner_radius(int64_t handle, int radius) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w || w->kind == EL_WIDGET_WINDOW) return;
        UIView* v = (UIView*)w->obj;
        v.layer.cornerRadius = (CGFloat)radius;
        v.clipsToBounds = YES;
    });
}

void el_uikit_widget_set_disabled(int64_t handle, int disabled) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w) return;
        switch (w->kind) {
            case EL_WIDGET_BUTTON:
                ((UIButton*)w->obj).enabled = !disabled;
                break;
            case EL_WIDGET_TEXTFIELD:
                ((UITextField*)w->obj).enabled = !disabled;
                break;
            case EL_WIDGET_TEXTAREA:
                ((UITextView*)w->obj).editable = !disabled;
                break;
            default:
                ((UIView*)w->obj).userInteractionEnabled = !disabled;
                break;
        }
    });
}

void el_uikit_widget_set_hidden(int64_t handle, int hidden) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w || w->kind == EL_WIDGET_WINDOW) return;
        ((UIView*)w->obj).hidden = (BOOL)hidden;
    });
}

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

/*
 * el_uikit_widget_add_child — attach child widget to parent.
 *
 * - Window:          child is added to the root UIStackView inside the root VC.
 * - VStack/HStack:   child is added as an arranged subview.
 * - ZStack:          child is added as a plain subview (caller manages layout).
 * - ScrollView:      child is added as a subview and pinned to content layout guide.
 * - Generic view:    child is added as a plain subview.
 */
void el_uikit_widget_add_child(int64_t parent, int64_t child) {
    el_uikit_sync_main(^{
        ElWidget* pw = el_widget_get(parent);
        ElWidget* cw = el_widget_get(child);
        if (!pw || !cw) return;
        if (cw->kind == EL_WIDGET_WINDOW) return; /* can't add window as subview */

        UIView* childView = (UIView*)cw->obj;

        switch (pw->kind) {
            case EL_WIDGET_WINDOW: {
                /* Dig to the root stack view: window → navCtrl → topVC → view → subviews[0] */
                UIWindow* win = (UIWindow*)pw->obj;
                UINavigationController* nav =
                    (UINavigationController*)win.rootViewController;
                UIView* rootVCView = nav.topViewController.view;
                UIStackView* stack = nil;
                for (UIView* sub in rootVCView.subviews) {
                    if ([sub isKindOfClass:[UIStackView class]]) {
                        stack = (UIStackView*)sub;
                        break;
                    }
                }
                if (stack) {
                    [stack addArrangedSubview:childView];
                } else {
                    [rootVCView addSubview:childView];
                }
                break;
            }
            case EL_WIDGET_VSTACK:
            case EL_WIDGET_HSTACK: {
                UIStackView* sv = (UIStackView*)pw->obj;
                [sv addArrangedSubview:childView];
                break;
            }
            case EL_WIDGET_SCROLL: {
                UIScrollView* sv = (UIScrollView*)pw->obj;
                [sv addSubview:childView];
                /* Pin child to content layout guide so scroll view knows content size. */
                UILayoutGuide* cg = sv.contentLayoutGuide;
                childView.translatesAutoresizingMaskIntoConstraints = NO;
                [NSLayoutConstraint activateConstraints:@[
                    [childView.topAnchor constraintEqualToAnchor:cg.topAnchor],
                    [childView.leadingAnchor constraintEqualToAnchor:cg.leadingAnchor],
                    [childView.trailingAnchor constraintEqualToAnchor:cg.trailingAnchor],
                    [childView.bottomAnchor constraintEqualToAnchor:cg.bottomAnchor],
                    /* Width matches scroll view's frame (vertical scroll only). */
                    [childView.widthAnchor constraintEqualToAnchor:sv.frameLayoutGuide.widthAnchor],
                ]];
                break;
            }
            case EL_WIDGET_ZSTACK:
            default: {
                UIView* pv = (UIView*)pw->obj;
                [pv addSubview:childView];
                break;
            }
        }
    });
}

/*
 * el_uikit_widget_remove_child — detach a child widget from its parent.
 */
void el_uikit_widget_remove_child(int64_t parent, int64_t child) {
    el_uikit_sync_main(^{
        ElWidget* pw = el_widget_get(parent);
        ElWidget* cw = el_widget_get(child);
        if (!cw || cw->kind == EL_WIDGET_WINDOW) return;
        UIView* childView = (UIView*)cw->obj;
        /* If parent is a stack view, remove from arranged subviews too. */
        if (pw && (pw->kind == EL_WIDGET_VSTACK || pw->kind == EL_WIDGET_HSTACK)) {
            UIStackView* sv = (UIStackView*)pw->obj;
            [sv removeArrangedSubview:childView];
        } else if (pw && pw->kind == EL_WIDGET_WINDOW) {
            /* Find and remove from root stack. */
            UIWindow* win = (UIWindow*)pw->obj;
            UINavigationController* nav =
                (UINavigationController*)win.rootViewController;
            for (UIView* sub in nav.topViewController.view.subviews) {
                if ([sub isKindOfClass:[UIStackView class]]) {
                    [(UIStackView*)sub removeArrangedSubview:childView];
                    break;
                }
            }
        }
        [childView removeFromSuperview];
    });
}

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

void el_uikit_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_uikit_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_uikit_widget_on_submit(int64_t handle, const char* fn_name) {
    /* For text fields, submit = return key → stored in cb_click slot. */
    el_uikit_widget_on_click(handle, fn_name);
}

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

void el_uikit_widget_destroy(int64_t handle) {
    el_uikit_sync_main(^{
        ElWidget* w = el_widget_get(handle);
        if (!w) return;
        if (w->kind != EL_WIDGET_WINDOW) {
            UIView* v = (UIView*)w->obj;
            /* If it's in a stack view, also remove from arranged subviews. */
            if ([v.superview isKindOfClass:[UIStackView class]]) {
                [(UIStackView*)v.superview removeArrangedSubview:v];
            }
            [v removeFromSuperview];
        }
        [_el_delegates[handle] release];
        _el_delegates[handle] = nil;
        el_widget_free(handle);
    });
}

/* ── Manifest reader ──────────────────────────────────────────────────────── */

/*
 * el_uikit_manifest_read — read an app manifest file. Tries the given path
 * first; if not found, looks in the main bundle. Returns a C string with the
 * file contents (heap-allocated; caller must not free — matches AppKit bridge
 * convention of returning static/permanent lifetime strings from manifest).
 * Returns NULL on failure.
 */
const char* el_uikit_manifest_read(const char* path) {
    if (!path || !*path) return NULL;

    NSString* pathStr = [NSString stringWithUTF8String:path];
    NSString* content = [NSString stringWithContentsOfFile:pathStr
                                                  encoding:NSUTF8StringEncoding
                                                     error:nil];
    if (!content) {
        /* Try bundle resource lookup — strip to last component without extension. */
        NSString* baseName = [[pathStr lastPathComponent]
            stringByDeletingPathExtension];
        NSString* ext = [pathStr pathExtension];
        NSString* bundlePath = [[NSBundle mainBundle]
            pathForResource:baseName ofType:(ext.length ? ext : nil)];
        if (bundlePath) {
            content = [NSString stringWithContentsOfFile:bundlePath
                                                encoding:NSUTF8StringEncoding
                                                   error:nil];
        }
    }
    if (!content) return NULL;
    return strdup([content UTF8String]);
}

/* ── __widget_* C API (called from el_seed.c) ─────────────────────────────── */
/*
 * These are the symbols that el compiled programs actually call. They forward
 * directly to the el_uikit_* implementations above. el_val_t is int64_t (the
 * same type used by the AppKit bridge).
 */

void __native_init(void) {
    el_uikit_init();
}

void __native_run_loop(void) {
    /* el_main_entry_fn must be set by the caller before this. On iOS the
     * entry function pointer pattern is inverted vs macOS: the el program
     * packages up its "build UI" work into a lambda / function and registers
     * it via el_main_entry_fn, then calls __native_run_loop.
     * el_uikit_run_loop takes that fn pointer and passes it to UIApplicationMain. */
    el_uikit_run_loop(el_main_entry_fn);
}

el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
                          el_val_t min_width, el_val_t min_height) {
    return (el_val_t)el_uikit_window_create(
        (const char*)(uintptr_t)title,
        (int)width, (int)height,
        (int)min_width, (int)min_height);
}

void __window_show(el_val_t handle) {
    el_uikit_window_show((int64_t)handle);
}

void __window_set_title(el_val_t handle, el_val_t title) {
    el_uikit_window_set_title((int64_t)handle,
        (const char*)(uintptr_t)title);
}

el_val_t __vstack_create(el_val_t spacing) {
    return (el_val_t)el_uikit_vstack_create((int)spacing);
}

el_val_t __hstack_create(el_val_t spacing) {
    return (el_val_t)el_uikit_hstack_create((int)spacing);
}

el_val_t __zstack_create(void) {
    return (el_val_t)el_uikit_zstack_create();
}

el_val_t __scroll_create(void) {
    return (el_val_t)el_uikit_scroll_create();
}

el_val_t __label_create(el_val_t text) {
    return (el_val_t)el_uikit_label_create((const char*)(uintptr_t)text);
}

el_val_t __button_create(el_val_t label) {
    return (el_val_t)el_uikit_button_create((const char*)(uintptr_t)label);
}

el_val_t __text_field_create(el_val_t placeholder) {
    return (el_val_t)el_uikit_text_field_create(
        (const char*)(uintptr_t)placeholder);
}

el_val_t __text_area_create(el_val_t placeholder) {
    return (el_val_t)el_uikit_text_area_create(
        (const char*)(uintptr_t)placeholder);
}

el_val_t __image_create(el_val_t path_or_name) {
    return (el_val_t)el_uikit_image_create(
        (const char*)(uintptr_t)path_or_name);
}

void __widget_set_text(el_val_t handle, el_val_t text) {
    el_uikit_widget_set_text((int64_t)handle, (const char*)(uintptr_t)text);
}

el_val_t __widget_get_text(el_val_t handle) {
    return (el_val_t)(uintptr_t)el_uikit_widget_get_text((int64_t)handle);
}

void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
                         el_val_t b, el_val_t a) {
    el_uikit_widget_set_color((int64_t)handle,
        (float)r, (float)g, (float)b, (float)a);
}

void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
                            el_val_t b, el_val_t a) {
    el_uikit_widget_set_bg_color((int64_t)handle,
        (float)r, (float)g, (float)b, (float)a);
}

void __widget_set_font(el_val_t handle, el_val_t family,
                        el_val_t size, el_val_t bold) {
    el_uikit_widget_set_font((int64_t)handle,
        (const char*)(uintptr_t)family, (int)size, (int)bold);
}

void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
                           el_val_t bottom, el_val_t left) {
    el_uikit_widget_set_padding((int64_t)handle,
        (int)top, (int)right, (int)bottom, (int)left);
}

void __widget_set_width(el_val_t handle, el_val_t width) {
    el_uikit_widget_set_width((int64_t)handle, (int)width);
}

void __widget_set_height(el_val_t handle, el_val_t height) {
    el_uikit_widget_set_height((int64_t)handle, (int)height);
}

void __widget_set_flex(el_val_t handle, el_val_t flex) {
    el_uikit_widget_set_flex((int64_t)handle, (int)flex);
}

void __widget_set_corner_radius(el_val_t handle, el_val_t radius) {
    el_uikit_widget_set_corner_radius((int64_t)handle, (int)radius);
}

void __widget_set_disabled(el_val_t handle, el_val_t disabled) {
    el_uikit_widget_set_disabled((int64_t)handle, (int)disabled);
}

void __widget_set_hidden(el_val_t handle, el_val_t hidden) {
    el_uikit_widget_set_hidden((int64_t)handle, (int)hidden);
}

void __widget_add_child(el_val_t parent, el_val_t child) {
    el_uikit_widget_add_child((int64_t)parent, (int64_t)child);
}

void __widget_remove_child(el_val_t parent, el_val_t child) {
    el_uikit_widget_remove_child((int64_t)parent, (int64_t)child);
}

void __widget_destroy(el_val_t handle) {
    el_uikit_widget_destroy((int64_t)handle);
}

void __widget_on_click(el_val_t handle, el_val_t fn_name) {
    el_uikit_widget_on_click((int64_t)handle,
        (const char*)(uintptr_t)fn_name);
}

void __widget_on_change(el_val_t handle, el_val_t fn_name) {
    el_uikit_widget_on_change((int64_t)handle,
        (const char*)(uintptr_t)fn_name);
}

void __widget_on_submit(el_val_t handle, el_val_t fn_name) {
    el_uikit_widget_on_submit((int64_t)handle,
        (const char*)(uintptr_t)fn_name);
}

el_val_t __manifest_read(el_val_t path) {
    return (el_val_t)(uintptr_t)el_uikit_manifest_read(
        (const char*)(uintptr_t)path);
}

#endif /* EL_TARGET_IOS */
