/*
 * el_runtime_win32.c — Minimal Win32-compatible el runtime stub.
 *
 * Provides only the symbols that native-hello (and the el-native vessel) call
 * from el_runtime.h. Replaces the full el_runtime.c + el_seed.c for
 * cross-compiled Win32 targets where POSIX headers (pthread, curl, dlfcn,
 * sys/socket, etc.) are unavailable.
 *
 * Symbols provided:
 *   Allocators:  el_strdup, el_strbuf, el_wrap_str (static helpers)
 *   I/O:         println, print
 *   String:      el_str_concat, str_eq, str_starts_with, str_len, str_concat,
 *                int_to_str, str_to_int, native_str_to_int, str_slice, str_trim,
 *                str_contains, str_ends_with, str_to_upper, str_to_lower,
 *                str_index_of, str_char_at, str_char_code, str_replace,
 *                str_pad_left, str_pad_right, str_upper, str_lower
 *   Math:        el_abs, el_max, el_min, int_to_float, float_to_int
 *   List:        el_list_empty, el_list_new, el_list_len, el_list_get,
 *                el_list_append, el_list_clone
 *   Map:         el_map_new, el_map_get, el_get_field, el_map_set
 *   Retain/RC:   el_retain, el_release
 *   Arena:       el_arena_push, el_arena_pop
 *   JSON:        json_get_string, json_get_int, json_get_float, json_get_bool,
 *                json_get_raw, json_find_key (static), json_skip_value (static)
 *   Process:     exit_program, env, el_runtime_init_args
 *   Stubs:       All remaining el_runtime.h symbols as no-op / return-0 stubs
 *                so the linker is satisfied even if the app doesn't call them.
 *
 * el_request_start / el_request_end are provided as empty no-ops (no HTTP
 * server, no per-request arena needed for a native GUI app).
 *
 * Compile:
 *   x86_64-w64-mingw32-gcc -std=c11 -DEL_TARGET_WIN32 -I<runtime> \
 *       -c el_runtime_win32.c -o el_runtime_win32.o
 */

#include "el_runtime.h"

#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

/* ── Arena (single-threaded; no pthread needed) ─────────────────────────── */

#define EL_ARENA_INITIAL 512

typedef struct {
    char**  ptrs;
    size_t  count;
    size_t  cap;
} ElArena;

static ElArena _arena        = {NULL, 0, 0};
static int     _arena_active = 0;

static void el_arena_track(char* p) {
    if (!_arena_active || !p) return;
    if (_arena.count >= _arena.cap) {
        size_t nc = _arena.cap == 0 ? EL_ARENA_INITIAL : _arena.cap * 2;
        char** g  = realloc(_arena.ptrs, nc * sizeof(char*));
        if (!g) return;
        _arena.ptrs = g;
        _arena.cap  = nc;
    }
    _arena.ptrs[_arena.count++] = p;
}

void el_request_start(void) { _arena.count = 0; _arena_active = 1; }
void el_request_end(void) {
    _arena_active = 0;
    for (size_t i = 0; i < _arena.count; i++) free(_arena.ptrs[i]);
    _arena.count = 0;
}

#define EL_ARENA_SCOPE_DEPTH 32
static size_t _arena_scope[EL_ARENA_SCOPE_DEPTH];
static int    _arena_scope_depth = 0;

el_val_t el_arena_push(void) {
    if (!_arena_active) _arena_active = 1;
    if (_arena_scope_depth < EL_ARENA_SCOPE_DEPTH)
        _arena_scope[_arena_scope_depth++] = _arena.count;
    return (el_val_t)(int64_t)_arena.count;
}

el_val_t el_arena_pop(el_val_t mark) {
    size_t save = (size_t)(int64_t)mark;
    if (save > _arena.count) save = 0;
    for (size_t i = save; i < _arena.count; i++) {
        if (_arena.ptrs[i]) { free(_arena.ptrs[i]); _arena.ptrs[i] = NULL; }
    }
    _arena.count = save;
    if (_arena_scope_depth > 0) _arena_scope_depth--;
    if (save == 0) _arena_active = 0;
    return 0;
}

/* ── Allocator helpers ───────────────────────────────────────────────────── */

static char* el_strdup(const char* s) {
    if (!s) { char* p = strdup(""); el_arena_track(p); return p; }
    char* p = strdup(s);
    el_arena_track(p);
    return p;
}

static char* el_strbuf(size_t n) {
    char* p = malloc(n + 1);
    if (!p) { fputs("el_runtime_win32: out of memory\n", stderr); exit(1); }
    p[0] = '\0';
    el_arena_track(p);
    return p;
}

static el_val_t el_wrap_str(char* s) {
    return EL_STR(s);
}

/* ── I/O ─────────────────────────────────────────────────────────────────── */

el_val_t println(el_val_t s) {
    const char* str = EL_CSTR(s);
    puts(str ? str : "");
    return 0;
}

el_val_t print(el_val_t s) {
    const char* str = EL_CSTR(s);
    if (str) fputs(str, stdout);
    return 0;
}

el_val_t readline(void) {
    char buf[4096];
    if (!fgets(buf, sizeof(buf), stdin)) return el_wrap_str(el_strdup(""));
    size_t len = strlen(buf);
    if (len > 0 && buf[len - 1] == '\n') buf[len - 1] = '\0';
    return el_wrap_str(el_strdup(buf));
}

/* ── String builtins ─────────────────────────────────────────────────────── */

el_val_t el_str_concat(el_val_t av, el_val_t bv) {
    const char* a = EL_CSTR(av);
    const char* b = EL_CSTR(bv);
    if (!a) a = "";
    if (!b) b = "";
    size_t la = strlen(a), lb = strlen(b);
    char* out = el_strbuf(la + lb);
    memcpy(out, a, la);
    memcpy(out + la, b, lb);
    out[la + lb] = '\0';
    return el_wrap_str(out);
}

el_val_t str_eq(el_val_t av, el_val_t bv) {
    const char* a = EL_CSTR(av);
    const char* b = EL_CSTR(bv);
    if (!a || !b) return (el_val_t)(a == b);
    return (el_val_t)(strcmp(a, b) == 0);
}

el_val_t str_starts_with(el_val_t sv, el_val_t prefv) {
    const char* s = EL_CSTR(sv);
    const char* p = EL_CSTR(prefv);
    if (!s || !p) return 0;
    return (el_val_t)(strncmp(s, p, strlen(p)) == 0);
}

el_val_t str_ends_with(el_val_t sv, el_val_t sufv) {
    const char* s   = EL_CSTR(sv);
    const char* suf = EL_CSTR(sufv);
    if (!s || !suf) return 0;
    size_t ls = strlen(s), lsuf = strlen(suf);
    if (lsuf > ls) return 0;
    return (el_val_t)(strcmp(s + ls - lsuf, suf) == 0);
}

el_val_t str_len(el_val_t sv) {
    const char* s = EL_CSTR(sv);
    return s ? (el_val_t)strlen(s) : 0;
}

el_val_t str_concat(el_val_t a, el_val_t b) {
    return el_str_concat(a, b);
}

el_val_t int_to_str(el_val_t n) {
    char buf[32];
    snprintf(buf, sizeof(buf), "%lld", (long long)n);
    return el_wrap_str(el_strdup(buf));
}

el_val_t str_to_int(el_val_t sv) {
    const char* s = EL_CSTR(sv);
    if (!s) return 0;
    return (el_val_t)atoll(s);
}

el_val_t native_str_to_int(el_val_t sv) { return str_to_int(sv); }

el_val_t str_slice(el_val_t sv, el_val_t start, el_val_t end) {
    const char* s = EL_CSTR(sv);
    if (!s) return el_wrap_str(el_strdup(""));
    int64_t len = (int64_t)strlen(s);
    if (start < 0) start = 0;
    if (end > len) end = len;
    if (start >= end) return el_wrap_str(el_strdup(""));
    int64_t sz = end - start;
    char* out = el_strbuf((size_t)sz);
    memcpy(out, s + start, (size_t)sz);
    out[sz] = '\0';
    return el_wrap_str(out);
}

el_val_t str_contains(el_val_t sv, el_val_t subv) {
    const char* s   = EL_CSTR(sv);
    const char* sub = EL_CSTR(subv);
    if (!s || !sub) return 0;
    return (el_val_t)(strstr(s, sub) != NULL);
}

el_val_t str_replace(el_val_t sv, el_val_t fromv, el_val_t tov) {
    const char* s    = EL_CSTR(sv);
    const char* from = EL_CSTR(fromv);
    const char* to   = EL_CSTR(tov);
    if (!s || !from || !to) return el_wrap_str(el_strdup(s ? s : ""));
    size_t ls = strlen(s), lf = strlen(from), lt = strlen(to);
    if (lf == 0) return el_wrap_str(el_strdup(s));
    /* Count occurrences */
    size_t count = 0;
    const char* p = s;
    while ((p = strstr(p, from)) != NULL) { count++; p += lf; }
    size_t new_len = ls + count * (lt > lf ? lt - lf : 0) - count * (lf > lt ? lf - lt : 0);
    char* out = el_strbuf(new_len);
    const char* src = s;
    char* dst = out;
    while ((p = strstr(src, from)) != NULL) {
        size_t pre = (size_t)(p - src);
        memcpy(dst, src, pre); dst += pre;
        memcpy(dst, to, lt);  dst += lt;
        src = p + lf;
    }
    strcpy(dst, src);
    return el_wrap_str(out);
}

el_val_t str_to_upper(el_val_t sv) {
    const char* s = EL_CSTR(sv);
    if (!s) return el_wrap_str(el_strdup(""));
    size_t n = strlen(s);
    char* out = el_strbuf(n);
    for (size_t i = 0; i < n; i++) out[i] = (char)toupper((unsigned char)s[i]);
    out[n] = '\0';
    return el_wrap_str(out);
}

el_val_t str_to_lower(el_val_t sv) {
    const char* s = EL_CSTR(sv);
    if (!s) return el_wrap_str(el_strdup(""));
    size_t n = strlen(s);
    char* out = el_strbuf(n);
    for (size_t i = 0; i < n; i++) out[i] = (char)tolower((unsigned char)s[i]);
    out[n] = '\0';
    return el_wrap_str(out);
}

el_val_t str_upper(el_val_t sv) { return str_to_upper(sv); }
el_val_t str_lower(el_val_t sv) { return str_to_lower(sv); }

el_val_t str_trim(el_val_t sv) {
    const char* s = EL_CSTR(sv);
    if (!s) return el_wrap_str(el_strdup(""));
    while (*s && isspace((unsigned char)*s)) s++;
    size_t n = strlen(s);
    while (n > 0 && isspace((unsigned char)s[n - 1])) n--;
    char* out = el_strbuf(n);
    memcpy(out, s, n);
    out[n] = '\0';
    return el_wrap_str(out);
}

el_val_t str_index_of(el_val_t sv, el_val_t subv) {
    const char* s   = EL_CSTR(sv);
    const char* sub = EL_CSTR(subv);
    if (!s || !sub) return -1;
    const char* p = strstr(s, sub);
    return p ? (el_val_t)(p - s) : -1;
}

el_val_t str_char_at(el_val_t sv, el_val_t i) {
    const char* s = EL_CSTR(sv);
    if (!s) return 0;
    int64_t idx = (int64_t)i;
    int64_t len = (int64_t)strlen(s);
    if (idx < 0 || idx >= len) return 0;
    char buf[2] = { s[idx], '\0' };
    return el_wrap_str(el_strdup(buf));
}

el_val_t str_char_code(el_val_t sv, el_val_t i) {
    const char* s = EL_CSTR(sv);
    if (!s) return 0;
    int64_t idx = (int64_t)i;
    int64_t len = (int64_t)strlen(s);
    if (idx < 0 || idx >= len) return 0;
    return (el_val_t)(unsigned char)s[idx];
}

el_val_t str_pad_left(el_val_t sv, el_val_t widthv, el_val_t padv) {
    const char* s   = EL_CSTR(sv);
    const char* pad = EL_CSTR(padv);
    if (!s) s = "";
    if (!pad || !*pad) pad = " ";
    int64_t width = (int64_t)widthv;
    int64_t slen  = (int64_t)strlen(s);
    if (slen >= width) return el_wrap_str(el_strdup(s));
    int64_t need = width - slen;
    char* out = el_strbuf((size_t)width);
    size_t plen = strlen(pad);
    for (int64_t i = 0; i < need; i++) out[i] = pad[i % plen];
    memcpy(out + need, s, (size_t)slen);
    out[width] = '\0';
    return el_wrap_str(out);
}

el_val_t str_pad_right(el_val_t sv, el_val_t widthv, el_val_t padv) {
    const char* s   = EL_CSTR(sv);
    const char* pad = EL_CSTR(padv);
    if (!s) s = "";
    if (!pad || !*pad) pad = " ";
    int64_t width = (int64_t)widthv;
    int64_t slen  = (int64_t)strlen(s);
    if (slen >= width) return el_wrap_str(el_strdup(s));
    int64_t need = width - slen;
    char* out = el_strbuf((size_t)width);
    memcpy(out, s, (size_t)slen);
    size_t plen = strlen(pad);
    for (int64_t i = 0; i < need; i++) out[slen + i] = pad[i % plen];
    out[width] = '\0';
    return el_wrap_str(out);
}

el_val_t str_format(el_val_t fmtv, el_val_t datav) {
    /* Minimal stub — just return the format string unchanged. */
    return fmtv;
}

el_val_t str_split(el_val_t sv, el_val_t sepv) {
    /* Return empty list stub. */
    return el_list_empty();
}

el_val_t str_count(el_val_t sv, el_val_t subv) {
    const char* s   = EL_CSTR(sv);
    const char* sub = EL_CSTR(subv);
    if (!s || !sub || !*sub) return 0;
    size_t lsub = strlen(sub);
    int64_t cnt = 0;
    const char* p = s;
    while ((p = strstr(p, sub)) != NULL) { cnt++; p += lsub; }
    return (el_val_t)cnt;
}

/* ── Math ────────────────────────────────────────────────────────────────── */

el_val_t el_abs(el_val_t n)             { return n < 0 ? -n : n; }
el_val_t el_max(el_val_t a, el_val_t b) { return a > b ? a : b; }
el_val_t el_min(el_val_t a, el_val_t b) { return a < b ? a : b; }

el_val_t int_to_float(el_val_t n)  { return el_from_float((double)(int64_t)n); }
el_val_t float_to_int(el_val_t f)  { return (el_val_t)(int64_t)el_to_float(f); }
el_val_t float_to_str(el_val_t f) {
    char buf[64];
    snprintf(buf, sizeof(buf), "%g", el_to_float(f));
    return el_wrap_str(el_strdup(buf));
}
el_val_t str_to_float(el_val_t sv) {
    const char* s = EL_CSTR(sv);
    if (!s) return 0;
    return el_from_float(strtod(s, NULL));
}
el_val_t format_float(el_val_t f, el_val_t decimals) {
    char buf[128];
    int d = (int)(int64_t)decimals;
    if (d < 0) d = 0; if (d > 30) d = 30;
    snprintf(buf, sizeof(buf), "%.*f", d, el_to_float(f));
    return el_wrap_str(el_strdup(buf));
}
el_val_t decimal_round(el_val_t f, el_val_t decimals) { return format_float(f, decimals); }
el_val_t math_sqrt(el_val_t f)  { return el_from_float(sqrt(el_to_float(f))); }
el_val_t math_log(el_val_t f)   { return el_from_float(log10(el_to_float(f))); }
el_val_t math_ln(el_val_t f)    { return el_from_float(log(el_to_float(f))); }
el_val_t math_sin(el_val_t f)   { return el_from_float(sin(el_to_float(f))); }
el_val_t math_cos(el_val_t f)   { return el_from_float(cos(el_to_float(f))); }
el_val_t math_pi(void)          { return el_from_float(3.14159265358979323846); }

/* ── Refcounted heap objects ─────────────────────────────────────────────── */

#define EL_MAGIC_LIST  0xE15710A1u
#define EL_MAGIC_MAP   0xE19A704Bu

typedef struct { uint32_t magic; uint32_t refcount; } ElHeader;

typedef struct {
    ElHeader  hdr;
    int64_t   length;
    int64_t   capacity;
    el_val_t* elems;
} ElList;

typedef struct {
    ElHeader  hdr;
    int64_t   count;
    int64_t   capacity;
    el_val_t* keys;
    el_val_t* values;
} ElMap;

static int looks_like_heap_obj(el_val_t v) {
    if (v == 0) return 0;
    int64_t s = (int64_t)v;
    if (s > -0x10000 && s < 0x10000) return 0;
    uintptr_t p = (uintptr_t)v;
    if (p < 0x10000) return 0;
    if (p & 0x7) return 0;
    return 1;
}

void el_retain(el_val_t v) {
    if (!looks_like_heap_obj(v)) return;
    ElHeader* h = (ElHeader*)(uintptr_t)v;
    if (h->magic == EL_MAGIC_LIST || h->magic == EL_MAGIC_MAP) h->refcount++;
}

void el_release(el_val_t v) {
    if (!looks_like_heap_obj(v)) return;
    ElHeader* h = (ElHeader*)(uintptr_t)v;
    if (h->magic == EL_MAGIC_LIST) {
        if (h->refcount > 0 && --h->refcount == 0) {
            ElList* l = (ElList*)h;
            free(l->elems); h->magic = 0; free(l);
        }
    } else if (h->magic == EL_MAGIC_MAP) {
        if (h->refcount > 0 && --h->refcount == 0) {
            ElMap* m = (ElMap*)h;
            free(m->keys); free(m->values); h->magic = 0; free(m);
        }
    }
}

/* ── List ────────────────────────────────────────────────────────────────── */

static ElList* list_alloc(int64_t cap) {
    if (cap < 4) cap = 4;
    ElList* lst = malloc(sizeof(ElList));
    if (!lst) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); }
    lst->hdr.magic    = EL_MAGIC_LIST;
    lst->hdr.refcount = 1;
    lst->length       = 0;
    lst->capacity     = cap;
    lst->elems        = malloc((size_t)cap * sizeof(el_val_t));
    if (!lst->elems) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); }
    return lst;
}

el_val_t el_list_empty(void) { return EL_STR(list_alloc(4)); }

el_val_t el_list_new(el_val_t count, ...) {
    ElList* lst = list_alloc(count > 0 ? count : 4);
    va_list ap; va_start(ap, count);
    for (int64_t i = 0; i < count; i++) lst->elems[i] = va_arg(ap, el_val_t);
    va_end(ap);
    lst->length = count;
    return EL_STR(lst);
}

el_val_t el_list_len(el_val_t lv) {
    ElList* l = (ElList*)(uintptr_t)lv;
    return l ? l->length : 0;
}

el_val_t el_list_get(el_val_t lv, el_val_t idx) {
    ElList* l = (ElList*)(uintptr_t)lv;
    if (!l || idx < 0 || idx >= l->length) return 0;
    return l->elems[idx];
}

el_val_t el_list_append(el_val_t lv, el_val_t elem) {
    ElList* old = (ElList*)(uintptr_t)lv;
    if (!old) {
        ElList* fresh = list_alloc(4);
        fresh->elems[0] = elem;
        fresh->length   = 1;
        return EL_STR(fresh);
    }
    if (old->hdr.refcount <= 1) {
        if (old->length >= old->capacity) {
            int64_t nc = old->capacity > 0 ? old->capacity * 2 : 4;
            el_val_t* g = realloc(old->elems, (size_t)nc * sizeof(el_val_t));
            if (!g) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); }
            old->elems    = g;
            old->capacity = nc;
        }
        old->elems[old->length++] = elem;
        return lv;
    }
    int64_t nc = old->length + 1;
    if (nc < 4) nc = 4;
    ElList* fresh = malloc(sizeof(ElList));
    if (!fresh) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); }
    fresh->hdr.magic    = EL_MAGIC_LIST;
    fresh->hdr.refcount = 1;
    fresh->length       = old->length + 1;
    fresh->capacity     = nc;
    fresh->elems        = malloc((size_t)nc * sizeof(el_val_t));
    if (!fresh->elems) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); }
    if (old->length > 0)
        memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t));
    fresh->elems[old->length] = elem;
    return EL_STR(fresh);
}

el_val_t el_list_clone(el_val_t lv) {
    ElList* old = (ElList*)(uintptr_t)lv;
    if (!old) return el_list_empty();
    ElList* fresh = list_alloc(old->capacity > 0 ? old->capacity : 4);
    if (old->length > 0)
        memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t));
    fresh->length = old->length;
    return EL_STR(fresh);
}

/* ── Map ─────────────────────────────────────────────────────────────────── */

static ElMap* map_alloc(int64_t cap) {
    if (cap < 4) cap = 4;
    ElMap* m = malloc(sizeof(ElMap));
    if (!m) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); }
    m->hdr.magic    = EL_MAGIC_MAP;
    m->hdr.refcount = 1;
    m->count        = 0;
    m->capacity     = cap;
    m->keys         = malloc((size_t)cap * sizeof(el_val_t));
    m->values       = malloc((size_t)cap * sizeof(el_val_t));
    if (!m->keys || !m->values) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); }
    return m;
}

el_val_t el_map_new(el_val_t pair_count, ...) {
    ElMap* m = map_alloc(pair_count > 0 ? pair_count : 4);
    va_list ap; va_start(ap, pair_count);
    for (int64_t i = 0; i < pair_count; i++) {
        m->keys[i]   = va_arg(ap, el_val_t);
        m->values[i] = va_arg(ap, el_val_t);
    }
    va_end(ap);
    m->count = pair_count;
    return EL_STR(m);
}

el_val_t el_map_get(el_val_t mv, el_val_t kv) {
    ElMap* m = (ElMap*)(uintptr_t)mv;
    const char* key = EL_CSTR(kv);
    if (!m || !key) return 0;
    for (int64_t i = 0; i < m->count; i++) {
        const char* k = EL_CSTR(m->keys[i]);
        if (k && strcmp(k, key) == 0) return m->values[i];
    }
    return 0;
}

el_val_t el_get_field(el_val_t mv, el_val_t kv) { return el_map_get(mv, kv); }

el_val_t el_map_set(el_val_t mv, el_val_t kv, el_val_t value) {
    ElMap* m = (ElMap*)(uintptr_t)mv;
    if (!m) {
        ElMap* fresh = map_alloc(4);
        fresh->keys[0] = kv; fresh->values[0] = value; fresh->count = 1;
        return EL_STR(fresh);
    }
    const char* key = EL_CSTR(kv);
    if (key) {
        for (int64_t i = 0; i < m->count; i++) {
            const char* k = EL_CSTR(m->keys[i]);
            if (k && strcmp(k, key) == 0) { m->values[i] = value; return mv; }
        }
    }
    if (m->count >= m->capacity) {
        int64_t nc = m->capacity > 0 ? m->capacity * 2 : 4;
        el_val_t* gk = realloc(m->keys,   (size_t)nc * sizeof(el_val_t));
        el_val_t* gv = realloc(m->values, (size_t)nc * sizeof(el_val_t));
        if (!gk || !gv) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); }
        m->keys = gk; m->values = gv; m->capacity = nc;
    }
    m->keys[m->count] = kv; m->values[m->count] = value; m->count++;
    return mv;
}

/* ── JSON helpers ────────────────────────────────────────────────────────── */

typedef struct {
    const char* p;
    const char* end;
    int         err;
} JsonParser;

static void jp_skip_ws(JsonParser* jp) {
    while (jp->p < jp->end) {
        char c = *jp->p;
        if (c == ' ' || c == '\t' || c == '\n' || c == '\r') jp->p++;
        else break;
    }
}

static char* jp_parse_string_raw(JsonParser* jp) {
    if (jp->p >= jp->end || *jp->p != '"') { jp->err = 1; return el_strdup(""); }
    jp->p++;
    size_t cap = 32, len = 0;
    char* out = malloc(cap);
    if (!out) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); }
    while (jp->p < jp->end && *jp->p != '"') {
        char c = *jp->p++;
        if (c == '\\' && jp->p < jp->end) {
            char esc = *jp->p++;
            switch (esc) {
                case '"':  c = '"';  break;
                case '\\': c = '\\'; break;
                case '/':  c = '/';  break;
                case 'b':  c = '\b'; break;
                case 'f':  c = '\f'; break;
                case 'n':  c = '\n'; break;
                case 'r':  c = '\r'; break;
                case 't':  c = '\t'; break;
                case 'u':
                    for (int i = 0; i < 4 && jp->p < jp->end; i++) jp->p++;
                    c = '?';
                    break;
                default: c = esc; break;
            }
        }
        if (len + 1 >= cap) {
            cap *= 2;
            out = realloc(out, cap);
            if (!out) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); }
        }
        out[len++] = c;
    }
    if (jp->p < jp->end && *jp->p == '"') jp->p++;
    else jp->err = 1;
    out[len] = '\0';
    return out;
}

static const char* json_skip_value(const char* p) {
    if (!p || !*p) return p;
    while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++;
    if (*p == '"') {
        p++;
        int e = 0;
        while (*p) {
            if (e) { e = 0; p++; continue; }
            if (*p == '\\') { e = 1; p++; continue; }
            if (*p == '"') { p++; break; }
            p++;
        }
        return p;
    }
    if (*p == '{' || *p == '[') {
        char open = *p, close = (open == '{') ? '}' : ']';
        int depth = 0, in_str = 0, e = 0;
        while (*p) {
            char c = *p;
            if (in_str) {
                if (e) e = 0;
                else if (c == '\\') e = 1;
                else if (c == '"') in_str = 0;
                p++; continue;
            }
            if (c == '"') { in_str = 1; p++; continue; }
            if (c == open) depth++;
            else if (c == close) { depth--; p++; if (!depth) return p; continue; }
            p++;
        }
        return p;
    }
    while (*p && *p != ',' && *p != '}' && *p != ']' &&
           *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') p++;
    return p;
}

static const char* json_find_key(const char* s, const char* key) {
    if (!s || !key) return NULL;
    size_t klen = strlen(key);
    int depth = 0, in_str = 0, escape = 0;
    const char* p = s;
    while (*p) {
        char c = *p;
        if (in_str) {
            if (escape) escape = 0;
            else if (c == '\\') escape = 1;
            else if (c == '"') {
                p++; in_str = 0; continue;
            }
            p++; continue;
        }
        if (c == '"') {
            const char* str_start = p + 1;
            const char* q = str_start;
            int e = 0;
            while (*q) {
                if (e) { e = 0; q++; continue; }
                if (*q == '\\') { e = 1; q++; continue; }
                if (*q == '"') break;
                q++;
            }
            size_t slen = (size_t)(q - str_start);
            const char* after = (*q == '"') ? q + 1 : q;
            if (depth == 1 && slen == klen && strncmp(str_start, key, klen) == 0) {
                const char* r = after;
                while (*r == ' ' || *r == '\t' || *r == '\n' || *r == '\r') r++;
                if (*r == ':') {
                    r++;
                    while (*r == ' ' || *r == '\t' || *r == '\n' || *r == '\r') r++;
                    return r;
                }
            }
            p = after; continue;
        }
        if (c == '{' || c == '[') depth++;
        else if (c == '}' || c == ']') depth--;
        p++;
    }
    return NULL;
}

el_val_t json_get_string(el_val_t json_str, el_val_t key) {
    const char* json = EL_CSTR(json_str);
    const char* k    = EL_CSTR(key);
    const char* p = json_find_key(json, k);
    if (!p || *p != '"') return el_wrap_str(el_strdup(""));
    JsonParser jp = { p, json + (json ? strlen(json) : 0), 0 };
    char* parsed = jp_parse_string_raw(&jp);
    if (jp.err) { free(parsed); return el_wrap_str(el_strdup("")); }
    return el_wrap_str(parsed);
}

el_val_t json_get_int(el_val_t json_str, el_val_t key) {
    const char* json = EL_CSTR(json_str);
    const char* k    = EL_CSTR(key);
    const char* p = json_find_key(json, k);
    if (!p || *p == '"' || *p == '{' || *p == '[') return 0;
    return (el_val_t)strtoll(p, NULL, 10);
}

el_val_t json_get_float(el_val_t json_str, el_val_t key) {
    const char* json = EL_CSTR(json_str);
    const char* k    = EL_CSTR(key);
    const char* p = json_find_key(json, k);
    if (!p || *p == '"' || *p == '{' || *p == '[') return 0;
    return el_from_float(strtod(p, NULL));
}

el_val_t json_get_bool(el_val_t json_str, el_val_t key) {
    const char* json = EL_CSTR(json_str);
    const char* k    = EL_CSTR(key);
    const char* p = json_find_key(json, k);
    if (!p) return 0;
    return (el_val_t)(strncmp(p, "true", 4) == 0);
}

el_val_t json_get_raw(el_val_t json_str, el_val_t key) {
    const char* json = EL_CSTR(json_str);
    const char* k    = EL_CSTR(key);
    const char* p = json_find_key(json, k);
    if (!p) return el_wrap_str(el_strdup(""));
    const char* end = json_skip_value(p);
    size_t n = (size_t)(end - p);
    char* out = el_strbuf(n);
    memcpy(out, p, n);
    out[n] = '\0';
    return el_wrap_str(out);
}

el_val_t json_get(el_val_t json, el_val_t key) {
    return json_get_raw(json, key);
}

/* Simple JSON number / scalar parser helpers for json_parse / json_stringify */

el_val_t json_parse(el_val_t s) {
    /* Minimal: return the string verbatim (caller accesses via json_get_*). */
    return s;
}

el_val_t json_stringify(el_val_t v) {
    /* Basic: if it already looks like a string, return it. */
    const char* s = EL_CSTR(v);
    return s ? v : EL_STR("{}");
}

el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value) {
    /* Stub: returns the original JSON unchanged. */
    return json_str;
}

el_val_t json_array_len(el_val_t json_str) {
    const char* s = EL_CSTR(json_str);
    if (!s || *s != '[') return 0;
    int64_t cnt = 0;
    int depth = 0, in_str = 0, e = 0;
    const char* p = s;
    while (*p) {
        char c = *p;
        if (in_str) {
            if (e) e = 0; else if (c == '\\') e = 1; else if (c == '"') in_str = 0;
            p++; continue;
        }
        if (c == '"') { in_str = 1; p++; continue; }
        if (c == '[' || c == '{') { if (depth++ == 0 && c == '[') { p++; continue; } }
        else if (c == ']' || c == '}') { if (--depth == 0) break; }
        else if (c == ',' && depth == 1) cnt++;
        p++;
    }
    /* If we saw any content, count = separators + 1 */
    if (depth == 0) { /* empty or done */ }
    /* Simpler: count commas at depth 1 + 1 if non-empty */
    /* Re-scan for simplicity */
    cnt = 0;
    depth = 0; in_str = 0; e = 0;
    p = s;
    int has_content = 0;
    while (*p) {
        char c = *p;
        if (in_str) {
            if (e) e = 0; else if (c == '\\') e = 1; else if (c == '"') in_str = 0;
            p++; continue;
        }
        if (c == '"') { in_str = 1; p++; continue; }
        if (c == '[' || c == '{') depth++;
        else if (c == ']' || c == '}') {
            depth--;
            if (depth == 0) break;
        }
        if (depth == 1) {
            if (c != '[' && c != ' ' && c != '\t' && c != '\n' && c != '\r') has_content = 1;
            if (c == ',') cnt++;
        }
        p++;
    }
    return has_content ? cnt + 1 : 0;
}

el_val_t json_array_get(el_val_t json_str, el_val_t index) {
    const char* s = EL_CSTR(json_str);
    if (!s || *s != '[') return 0;
    int64_t target = (int64_t)index;
    int64_t cur = 0;
    int depth = 0, in_str = 0, e = 0;
    const char* p = s;
    while (*p) {
        char c = *p;
        if (in_str) {
            if (e) e = 0; else if (c == '\\') e = 1; else if (c == '"') in_str = 0;
            p++; continue;
        }
        if (c == '"') { in_str = 1; }
        if (c == '[' || c == '{') depth++;
        else if (c == ']' || c == '}') { if (--depth == 0) break; }
        if (depth == 1 && (p == s + 1 || c == ',')) {
            if (c == ',') { cur++; p++; while (*p == ' ' || *p == '\t' || *p == '\n') p++; }
            else { p++; while (*p == ' ' || *p == '\t' || *p == '\n') p++; }
            if (cur == target) {
                const char* end = json_skip_value(p);
                size_t n = (size_t)(end - p);
                char* out = el_strbuf(n);
                memcpy(out, p, n); out[n] = '\0';
                return el_wrap_str(out);
            }
            continue;
        }
        p++;
    }
    return 0;
}

el_val_t json_array_get_string(el_val_t json_str, el_val_t index) {
    el_val_t raw = json_array_get(json_str, index);
    if (!raw) return el_wrap_str(el_strdup(""));
    const char* s = EL_CSTR(raw);
    if (!s || *s != '"') return raw;
    JsonParser jp = { s, s + strlen(s), 0 };
    char* parsed = jp_parse_string_raw(&jp);
    return el_wrap_str(parsed);
}

el_val_t json_escape_string(el_val_t sv) {
    const char* s = EL_CSTR(sv);
    if (!s) return el_wrap_str(el_strdup("\"\""));
    size_t n = strlen(s);
    char* out = el_strbuf(n * 6 + 4);
    char* p = out;
    *p++ = '"';
    for (size_t i = 0; i < n; i++) {
        unsigned char c = (unsigned char)s[i];
        switch (c) {
            case '"':  *p++ = '\\'; *p++ = '"';  break;
            case '\\': *p++ = '\\'; *p++ = '\\'; break;
            case '\n': *p++ = '\\'; *p++ = 'n';  break;
            case '\r': *p++ = '\\'; *p++ = 'r';  break;
            case '\t': *p++ = '\\'; *p++ = 't';  break;
            default:
                if (c < 0x20) {
                    p += sprintf(p, "\\u%04x", c);
                } else {
                    *p++ = (char)c;
                }
        }
    }
    *p++ = '"'; *p = '\0';
    return el_wrap_str(out);
}

el_val_t json_build_object(el_val_t kvs) { return kvs; }
el_val_t json_build_array(el_val_t items) { return items; }

/* ── Environment / process ───────────────────────────────────────────────── */

el_val_t env(el_val_t key) {
    const char* k = EL_CSTR(key);
    if (!k) return el_wrap_str(el_strdup(""));
    const char* v = getenv(k);
    return el_wrap_str(el_strdup(v ? v : ""));
}

el_val_t exit_program(el_val_t code) {
    exit((int)(int64_t)code);
    return 0;
}

/* Args store — populated by el_runtime_init_args */
static el_val_t _el_args_list = 0;

void el_runtime_init_args(int argc, char** argv) {
    _el_args_list = el_list_empty();
    for (int i = 1; i < argc; i++) {
        _el_args_list = el_list_append(_el_args_list, EL_STR(argv[i]));
    }
}

el_val_t args(void) {
    if (!_el_args_list) _el_args_list = el_list_empty();
    return _el_args_list;
}

/* ── HTML helpers (stubs) ────────────────────────────────────────────────── */

el_val_t el_html_sanitize(el_val_t input_html, el_val_t allowlist_json) { return input_html; }
el_val_t html_raw(el_val_t s) { return s; }
el_val_t html_escape(el_val_t s) { return s; }
el_val_t url_encode(el_val_t s) { return s; }
el_val_t url_decode(el_val_t s) { return s; }

/* ── Filesystem stubs ────────────────────────────────────────────────────── */

el_val_t fs_read(el_val_t path) {
    const char* p = EL_CSTR(path);
    if (!p) return el_wrap_str(el_strdup(""));
    FILE* f = fopen(p, "rb");
    if (!f) return el_wrap_str(el_strdup(""));
    fseek(f, 0, SEEK_END);
    long sz = ftell(f);
    rewind(f);
    if (sz <= 0) { fclose(f); return el_wrap_str(el_strdup("")); }
    char* buf = malloc((size_t)sz + 1);
    if (!buf) { fclose(f); return el_wrap_str(el_strdup("")); }
    size_t got = fread(buf, 1, (size_t)sz, f);
    buf[got] = '\0';
    fclose(f);
    el_arena_track(buf);
    return EL_STR(buf);
}

el_val_t fs_write(el_val_t path, el_val_t content) {
    const char* p = EL_CSTR(path);
    const char* c = EL_CSTR(content);
    if (!p || !c) return 0;
    FILE* f = fopen(p, "wb");
    if (!f) return 0;
    fputs(c, f);
    fclose(f);
    return 1;
}

el_val_t fs_exists(el_val_t path) {
    const char* p = EL_CSTR(path);
    if (!p) return 0;
    FILE* f = fopen(p, "rb");
    if (!f) return 0;
    fclose(f);
    return 1;
}

el_val_t fs_list(el_val_t path) { return el_wrap_str(el_strdup("")); }
el_val_t fs_list_json(el_val_t path) { return el_wrap_str(el_strdup("[]")); }
el_val_t fs_mkdir(el_val_t path) { return 0; }
el_val_t fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t n) { return 0; }

/* ── HTTP stubs (no network in Win32 GUI app) ────────────────────────────── */

el_val_t http_get(el_val_t url) { return el_wrap_str(el_strdup("{\"error\":\"no http\"}")); }
el_val_t http_post(el_val_t url, el_val_t body) { return http_get(url); }
el_val_t http_post_json(el_val_t url, el_val_t body) { return http_get(url); }
el_val_t http_get_with_headers(el_val_t url, el_val_t h) { return http_get(url); }
el_val_t http_post_with_headers(el_val_t url, el_val_t b, el_val_t h) { return http_get(url); }
el_val_t http_post_json_with_headers(el_val_t url, el_val_t h, el_val_t b) { return http_get(url); }
el_val_t http_post_form_auth(el_val_t url, el_val_t b, el_val_t a) { return http_get(url); }
el_val_t http_delete(el_val_t url) { return http_get(url); }
el_val_t http_serve(el_val_t port, el_val_t handler) { return 0; }
el_val_t http_set_handler(el_val_t name) { return 0; }
el_val_t http_serve_v2(el_val_t port, el_val_t handler) { return 0; }
el_val_t http_set_handler_v2(el_val_t name) { return 0; }
el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body) { return body; }
void     el_seed_set_http_conn_fd(int fd) { (void)fd; }
el_val_t http_post_to_file(el_val_t url, el_val_t b, el_val_t h, el_val_t out) { return 0; }
el_val_t http_get_to_file(el_val_t url, el_val_t h, el_val_t out) { return 0; }

/* ── Time stubs ──────────────────────────────────────────────────────────── */

#include <time.h>

el_val_t time_now(void) { return (el_val_t)time(NULL); }
el_val_t time_now_utc(void) { return time_now(); }
el_val_t now_ns(void) { return (el_val_t)(time(NULL) * (int64_t)1000000000LL); }
el_val_t el_now_instant(void) { return now_ns(); }
el_val_t now(void) { return el_now_instant(); }
el_val_t unix_seconds(el_val_t n) { return n * 1000000000LL; }
el_val_t unix_millis(el_val_t n)  { return n * 1000000LL; }
el_val_t unix_timestamp(void) { return (el_val_t)time(NULL); }
el_val_t sleep_secs(el_val_t secs) { return 0; }
el_val_t sleep_ms(el_val_t ms) { return 0; }
el_val_t el_sleep_duration(el_val_t dur) { return 0; }
el_val_t time_format(el_val_t ts, el_val_t fmt) { return el_wrap_str(el_strdup("")); }
el_val_t time_to_parts(el_val_t ts) { return el_wrap_str(el_strdup("{}")); }
el_val_t time_from_parts(el_val_t s, el_val_t ns, el_val_t tz) { return s; }
el_val_t time_add(el_val_t ts, el_val_t n, el_val_t unit) { return ts; }
el_val_t time_diff(el_val_t a, el_val_t b, el_val_t unit) { return a - b; }
el_val_t instant_from_iso8601(el_val_t s) { return 0; }
el_val_t el_duration_from_nanos(el_val_t ns) { return ns; }
el_val_t duration_seconds(el_val_t n) { return n * 1000000000LL; }
el_val_t duration_millis(el_val_t n)  { return n * 1000000LL; }
el_val_t duration_nanos(el_val_t n)   { return n; }
el_val_t el_instant_add_dur(el_val_t i, el_val_t d)  { return i + d; }
el_val_t el_instant_sub_dur(el_val_t i, el_val_t d)  { return i - d; }
el_val_t el_instant_diff(el_val_t a, el_val_t b)     { return a - b; }
el_val_t el_duration_add(el_val_t a, el_val_t b)     { return a + b; }
el_val_t el_duration_sub(el_val_t a, el_val_t b)     { return a - b; }
el_val_t el_duration_scale(el_val_t d, el_val_t s)   { return d * s; }
el_val_t el_duration_div(el_val_t d, el_val_t s)     { return s ? d / s : 0; }
el_val_t el_instant_lt(el_val_t a, el_val_t b)  { return a < b; }
el_val_t el_instant_le(el_val_t a, el_val_t b)  { return a <= b; }
el_val_t el_instant_gt(el_val_t a, el_val_t b)  { return a > b; }
el_val_t el_instant_ge(el_val_t a, el_val_t b)  { return a >= b; }
el_val_t el_instant_eq(el_val_t a, el_val_t b)  { return a == b; }
el_val_t el_instant_ne(el_val_t a, el_val_t b)  { return a != b; }
el_val_t el_duration_lt(el_val_t a, el_val_t b) { return a < b; }
el_val_t el_duration_le(el_val_t a, el_val_t b) { return a <= b; }
el_val_t el_duration_gt(el_val_t a, el_val_t b) { return a > b; }
el_val_t el_duration_ge(el_val_t a, el_val_t b) { return a >= b; }
el_val_t el_duration_eq(el_val_t a, el_val_t b) { return a == b; }
el_val_t el_duration_ne(el_val_t a, el_val_t b) { return a != b; }
el_val_t instant_to_unix_seconds(el_val_t i) { return i / 1000000000LL; }
el_val_t instant_to_unix_millis(el_val_t i)  { return i / 1000000LL; }
el_val_t instant_to_iso8601(el_val_t i) { return el_wrap_str(el_strdup("")); }
el_val_t duration_to_seconds(el_val_t d) { return d / 1000000000LL; }
el_val_t duration_to_millis(el_val_t d)  { return d / 1000000LL; }
el_val_t duration_to_nanos(el_val_t d)   { return d; }

/* ── Calendar stubs ──────────────────────────────────────────────────────── */

el_val_t zone(el_val_t id) { return id; }
el_val_t zone_utc(void) { return EL_STR("UTC"); }
el_val_t zone_local(void) { return EL_STR("local"); }
el_val_t zone_offset(el_val_t h, el_val_t m) { return 0; }
el_val_t earth_calendar(el_val_t z) { return 0; }
el_val_t earth_calendar_default(void) { return 0; }
el_val_t mars_calendar(void) { return 0; }
el_val_t cycle_calendar(el_val_t p) { return 0; }
el_val_t no_cycle_calendar(void) { return 0; }
el_val_t relative_calendar(el_val_t e) { return 0; }
el_val_t now_in(el_val_t cal) { return 0; }
el_val_t in_calendar(el_val_t inst, el_val_t cal) { return 0; }
el_val_t cal_format(el_val_t ct, el_val_t pattern) { return el_wrap_str(el_strdup("")); }
el_val_t cal_to_instant(el_val_t ct) { return 0; }
el_val_t cal_cycle_phase(el_val_t ct) { return 0; }
el_val_t cal_in(el_val_t ct, el_val_t cal) { return 0; }
el_val_t local_date(el_val_t y, el_val_t m, el_val_t d) { return 0; }
el_val_t local_time(el_val_t h, el_val_t m, el_val_t s, el_val_t ns) { return 0; }
el_val_t local_datetime(el_val_t date, el_val_t time) { return 0; }
el_val_t zoned(el_val_t date, el_val_t time, el_val_t cal) { return 0; }
el_val_t local_date_year(el_val_t ld) { return 0; }
el_val_t local_date_month(el_val_t ld) { return 0; }
el_val_t local_date_day(el_val_t ld) { return 0; }
el_val_t local_time_hour(el_val_t lt) { return 0; }
el_val_t local_time_minute(el_val_t lt) { return 0; }
el_val_t local_time_second(el_val_t lt) { return 0; }
el_val_t local_time_nanos(el_val_t lt) { return 0; }
el_val_t el_local_date_add_dur(el_val_t ld, el_val_t dur) { return ld; }
el_val_t el_local_time_add_dur(el_val_t lt, el_val_t dur) { return lt; }
el_val_t el_local_date_lt(el_val_t a, el_val_t b) { return a < b; }
el_val_t el_local_date_eq(el_val_t a, el_val_t b) { return a == b; }
el_val_t rhythm_cycle_start(void) { return 0; }
el_val_t rhythm_cycle_phase(el_val_t p) { return 0; }
el_val_t rhythm_duration(el_val_t d) { return 0; }
el_val_t rhythm_session_start(void) { return 0; }
el_val_t rhythm_event(el_val_t name) { return 0; }
el_val_t rhythm_and(el_val_t a, el_val_t b) { return 0; }
el_val_t rhythm_or(el_val_t a, el_val_t b) { return 0; }
el_val_t rhythm_weekday(el_val_t day) { return 0; }
el_val_t rhythm_weekly_at(el_val_t day, el_val_t h, el_val_t m) { return 0; }
el_val_t rhythm_next_after(el_val_t r, el_val_t after, el_val_t cal) { return 0; }
el_val_t rhythm_matches(el_val_t r, el_val_t ct) { return 0; }

/* ── UUID stubs ──────────────────────────────────────────────────────────── */

el_val_t uuid_new(void) { return el_wrap_str(el_strdup("00000000-0000-0000-0000-000000000000")); }
el_val_t uuid_v4(void)  { return uuid_new(); }

/* ── State K/V stubs ─────────────────────────────────────────────────────── */

el_val_t state_set(el_val_t key, el_val_t value) { return value; }
el_val_t state_get(el_val_t key) { return 0; }
el_val_t state_del(el_val_t key) { return 0; }
el_val_t state_keys(void) { return el_wrap_str(el_strdup("[]")); }
el_val_t state_has(el_val_t key) { return 0; }
el_val_t state_get_or(el_val_t key, el_val_t def) { return def; }

/* ── TTL cache stubs ─────────────────────────────────────────────────────── */

el_val_t ttl_cache_set(el_val_t key, el_val_t value) { return value; }
el_val_t ttl_cache_get(el_val_t key, el_val_t max_age) { return 0; }
el_val_t ttl_cache_age(el_val_t key) { return -1; }
