/*
 * types.c — Value types, NaN-boxing, symbol interning, Env, Pair, etc.
 */
#include "lumbda.h"

/* ═══════════════════════════════════════════════════════════════════════════
 * Error context (thread-local)
 * ═══════════════════════════════════════════════════════════════════════════ */

__thread ErrorContext *g_error_ctx = NULL;

void lisp_error(const char *fmt, ...) {
    if (!g_error_ctx) {
        va_list ap;
        va_start(ap, fmt);
        vfprintf(stderr, fmt, ap);
        va_end(ap);
        fprintf(stderr, "\n");
        exit(1);
    }
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(g_error_ctx->message, MAX_ERROR_MSG, fmt, ap);
    va_end(ap);
    g_error_ctx->error_obj = VAL_NIL;
    longjmp(g_error_ctx->jmp, 1);
}

void lisp_error_with_obj(Value obj, const char *fmt, ...) {
    if (!g_error_ctx) {
        va_list ap;
        va_start(ap, fmt);
        vfprintf(stderr, fmt, ap);
        va_end(ap);
        fprintf(stderr, "\n");
        exit(1);
    }
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(g_error_ctx->message, MAX_ERROR_MSG, fmt, ap);
    va_end(ap);
    g_error_ctx->error_obj = obj;
    longjmp(g_error_ctx->jmp, 1);
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Value stack
 * ═══════════════════════════════════════════════════════════════════════════ */

/* Unused slots in s->data are zeroed so Boehm's conservative scan of
 * this GC_MALLOC region does not treat stale popped pointers as live
 * roots. Without this, popped Scheme values stay pinned for the
 * lifetime of the stack — every closure ever pushed leaks. */

void vs_init(ValueStack *s, int cap) {
    s->data = (Value *)ul_malloc_values(sizeof(Value) * cap);
    memset(s->data, 0, sizeof(Value) * cap);
    s->len = 0;
    s->cap = cap;
}

void vs_push(ValueStack *s, Value v) {
    if (s->len >= s->cap) {
        int old_cap = s->cap;
        s->cap = s->cap * 2;
        s->data = (Value *)ul_realloc_values(s->data, sizeof(Value) * s->cap);
        memset(s->data + old_cap, 0, sizeof(Value) * (s->cap - old_cap));
    }
    s->data[s->len++] = v;
}

Value vs_pop(ValueStack *s) {
    if (s->len <= 0) lisp_error("stack underflow");
    Value v = s->data[--s->len];
    s->data[s->len] = 0;
    return v;
}

Value vs_peek(ValueStack *s) {
    if (s->len <= 0) lisp_error("stack underflow");
    return s->data[s->len - 1];
}

void vs_clear(ValueStack *s) {
    if (s->len > 0) memset(s->data, 0, sizeof(Value) * s->len);
    s->len = 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Symbol interning
 * ═══════════════════════════════════════════════════════════════════════════ */

SymbolTable g_symbols = {0};

static uint32_t sym_hash(const char *s) {
    uint32_t h = 5381;
    while (*s) { h = ((h << 5) + h) + (unsigned char)*s++; }
    return h;
}

Value intern(const char *name) {
    uint32_t h = sym_hash(name) % SYMBOL_TABLE_SIZE;
    SymbolEntry *e = g_symbols.buckets[h];
    while (e) {
        if (strcmp(e->name, name) == 0) return e->value;
        e = e->next;
    }
    /* New symbol */
    char *copy = ul_strdup(name);
    SymbolEntry *ne = (SymbolEntry *)ul_malloc_values(sizeof(SymbolEntry));
    ne->name = copy;
    ne->value = VAL_SYM_RAW(copy);
    ne->next = g_symbols.buckets[h];
    g_symbols.buckets[h] = ne;
    return ne->value;
}

const char *sym_name(Value sym) {
    return (const char *)(uintptr_t)GET_PAYLOAD(sym);
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Pair
 * ═══════════════════════════════════════════════════════════════════════════ */

Pair *make_pair(Value car, Value cdr) {
    Pair *p = (Pair *)ul_malloc_values(sizeof(Pair));
    p->hdr.type = OBJ_PAIR;
    p->car = car;
    p->cdr = cdr;
    p->line = 0;
    return p;
}

Value cons(Value car, Value cdr) {
    return VAL_PTR(make_pair(car, cdr));
}

/* ═══════════════════════════════════════════════════════════════════════════
 * String
 * ═══════════════════════════════════════════════════════════════════════════ */

Value make_string(const char *s, size_t len, bool mutable) {
    ULString *str = (ULString *)ul_malloc(sizeof(ULString));
    str->hdr.type = mutable ? OBJ_MUTABLE_STRING : OBJ_STRING;
    str->data = (char *)ul_malloc(len + 1);
    memcpy(str->data, s, len);
    str->data[len] = '\0';
    str->len = len;
    str->mutable = mutable;
    return VAL_PTR(str);
}

Value make_string_from_cstr(const char *s) {
    return make_string(s, strlen(s), false);
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Vector
 * ═══════════════════════════════════════════════════════════════════════════ */

Value make_vector(size_t len, Value fill) {
    ULVector *v = (ULVector *)ul_malloc(sizeof(ULVector));
    v->hdr.type = OBJ_VECTOR;
    v->len = len;
    v->cap = len > 0 ? len : 4;
    v->data = (Value *)ul_malloc_values(sizeof(Value) * v->cap);
    for (size_t i = 0; i < len; i++) v->data[i] = fill;
    return VAL_PTR(v);
}

Value make_vector_from(Value *items, size_t len) {
    ULVector *v = (ULVector *)ul_malloc(sizeof(ULVector));
    v->hdr.type = OBJ_VECTOR;
    v->len = len;
    v->cap = len > 0 ? len : 4;
    v->data = (Value *)ul_malloc_values(sizeof(Value) * v->cap);
    memcpy(v->data, items, sizeof(Value) * len);
    return VAL_PTR(v);
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Hash table
 * ═══════════════════════════════════════════════════════════════════════════ */

static uint64_t value_hash(Value v);

Value make_hashtable(void) {
    ULHashTable *ht = (ULHashTable *)ul_malloc(sizeof(ULHashTable));
    ht->hdr.type = OBJ_HASHTABLE;
    ht->nbuckets = 64;
    ht->count = 0;
    ht->buckets = (HTEntry **)ul_malloc(sizeof(HTEntry *) * ht->nbuckets);
    memset(ht->buckets, 0, sizeof(HTEntry *) * ht->nbuckets);
    return VAL_PTR(ht);
}

static uint64_t value_hash(Value v) {
    if (IS_INT(v)) return (uint64_t)as_int(v) * 2654435761ULL;
    if (IS_SYM(v)) return sym_hash(sym_name(v));
    if (IS_STRING(v)) return sym_hash(AS_STRING(v)->data);
    if (IS_DOUBLE(v)) {
        double d = as_double(v);
        uint64_t bits;
        memcpy(&bits, &d, sizeof(bits));
        return bits * 2654435761ULL;
    }
    if (IS_SPECIAL(v)) return GET_PAYLOAD(v) * 2654435761ULL;
    /* For other types, use the raw bits */
    return v * 2654435761ULL;
}

void ht_set(ULHashTable *ht, Value key, Value val) {
    uint64_t h = value_hash(key) % ht->nbuckets;
    HTEntry *e = ht->buckets[h];
    while (e) {
        if (values_equal(e->key, key)) { e->value = val; return; }
        e = e->next;
    }
    HTEntry *ne = (HTEntry *)ul_malloc_values(sizeof(HTEntry));
    ne->key = key;
    ne->value = val;
    ne->next = ht->buckets[h];
    ht->buckets[h] = ne;
    ht->count++;
    /* Resize if load factor > 2 */
    if (ht->count > ht->nbuckets * 2) {
        size_t new_nbuckets = ht->nbuckets * 4;
        HTEntry **new_buckets = (HTEntry **)ul_malloc(sizeof(HTEntry *) * new_nbuckets);
        memset(new_buckets, 0, sizeof(HTEntry *) * new_nbuckets);
        for (size_t i = 0; i < ht->nbuckets; i++) {
            HTEntry *cur = ht->buckets[i];
            while (cur) {
                HTEntry *next = cur->next;
                uint64_t nh = value_hash(cur->key) % new_nbuckets;
                cur->next = new_buckets[nh];
                new_buckets[nh] = cur;
                cur = next;
            }
        }
        ul_free(ht->buckets);
        ht->buckets = new_buckets;
        ht->nbuckets = new_nbuckets;
    }
}

Value ht_ref(ULHashTable *ht, Value key, bool *found) {
    uint64_t h = value_hash(key) % ht->nbuckets;
    HTEntry *e = ht->buckets[h];
    while (e) {
        if (values_equal(e->key, key)) { *found = true; return e->value; }
        e = e->next;
    }
    *found = false;
    return VAL_NIL;
}

bool ht_delete(ULHashTable *ht, Value key) {
    uint64_t h = value_hash(key) % ht->nbuckets;
    HTEntry **pp = &ht->buckets[h];
    while (*pp) {
        if (values_equal((*pp)->key, key)) {
            HTEntry *del = *pp;
            *pp = del->next;
            ul_free(del);
            ht->count--;
            return true;
        }
        pp = &(*pp)->next;
    }
    return false;
}

size_t ht_count(ULHashTable *ht) {
    return ht->count;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Rational numbers (simple fraction type)
 * ═══════════════════════════════════════════════════════════════════════════ */

static int64_t gcd64(int64_t a, int64_t b) {
    if (a < 0) a = -a;
    if (b < 0) b = -b;
    while (b) { int64_t t = b; b = a % b; a = t; }
    return a;
}

Value make_rational(int64_t num, int64_t den) {
    Rational *r = (Rational *)ul_malloc(sizeof(Rational));
    r->hdr.type = OBJ_RATIONAL;
    r->num = num;
    r->den = den;
    return NANBOX(TAG_RATIONAL, (uintptr_t)r);
}

Value rational_normalize(int64_t num, int64_t den) {
    if (den == 0) lisp_error("division by zero");
    if (den < 0) { num = -num; den = -den; }
    if (num == 0) return VAL_INT(0);
    int64_t g = gcd64(num, den);
    num /= g; den /= g;
    if (den == 1) return VAL_INT(num);
    return make_rational(num, den);
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Number operations
 * ═══════════════════════════════════════════════════════════════════════════ */

double as_number_double(Value v) {
    if (IS_INT(v)) return (double)as_int(v);
    if (IS_DOUBLE(v)) return as_double(v);
    if (IS_RATIONAL(v)) {
        Rational *r = AS_RATIONAL(v);
        return (double)r->num / (double)r->den;
    }
    if (IS_BIGNUM(v)) {
        Bignum *b = AS_BIGNUM(v);
        double d = 0.0;
        for (int32_t i = (int32_t)b->n_limbs - 1; i >= 0; i--)
            d = d * 18446744073709551616.0 + (double)b->limbs[i];
        return b->sign < 0 ? -d : d;
    }
    lisp_error("not a number");
    return 0;
}

int64_t as_number_int(Value v) {
    if (IS_INT(v)) return as_int(v);
    if (IS_DOUBLE(v)) return (int64_t)as_double(v);
    if (IS_RATIONAL(v)) {
        Rational *r = AS_RATIONAL(v);
        return r->num / r->den;
    }
    if (IS_BIGNUM(v)) {
        Bignum *b = AS_BIGNUM(v);
        uint64_t lo = b->n_limbs > 0 ? b->limbs[0] : 0;
        int64_t s = (int64_t)lo;
        return b->sign < 0 ? -s : s;
    }
    lisp_error("not a number");
    return 0;
}

/* Exact arithmetic helpers */
static void to_rational(Value v, int64_t *num, int64_t *den) {
    if (IS_INT(v)) { *num = as_int(v); *den = 1; }
    else if (IS_RATIONAL(v)) { Rational *r = AS_RATIONAL(v); *num = r->num; *den = r->den; }
    else { *num = 0; *den = 1; } /* shouldn't happen */
}

static bool is_exact(Value v) {
    return IS_INT(v) || IS_RATIONAL(v) || IS_BIGNUM(v);
}

/* Fixnum add/sub/mul that promote on overflow. Result is fixnum or bignum. */
static Value fixnum_add(int64_t a, int64_t b) {
    int64_t r;
    if (__builtin_add_overflow(a, b, &r) || !FITS_FIXNUM(r))
        return big_add(make_bignum_from_i64(a), make_bignum_from_i64(b));
    return VAL_INT(r);
}
static Value fixnum_sub(int64_t a, int64_t b) {
    int64_t r;
    if (__builtin_sub_overflow(a, b, &r) || !FITS_FIXNUM(r))
        return big_sub(make_bignum_from_i64(a), make_bignum_from_i64(b));
    return VAL_INT(r);
}
static Value fixnum_mul(int64_t a, int64_t b) {
    int64_t r;
    if (__builtin_mul_overflow(a, b, &r) || !FITS_FIXNUM(r))
        return big_mul(make_bignum_from_i64(a), make_bignum_from_i64(b));
    return VAL_INT(r);
}

Value num_add(Value a, Value b) {
    /* Pure integer path (fixnum or bignum) — never produces a rational. */
    if (IS_INTEGER(a) && IS_INTEGER(b)) {
        if (IS_INT(a) && IS_INT(b)) return fixnum_add(as_int(a), as_int(b));
        return big_add(a, b);
    }
    if (is_exact(a) && is_exact(b)) {
        /* Rational mix — fall back to int64_t numerator/denominator path. */
        int64_t an, ad, bn, bd;
        to_rational(a, &an, &ad);
        to_rational(b, &bn, &bd);
        return rational_normalize(an * bd + bn * ad, ad * bd);
    }
    return make_double(as_number_double(a) + as_number_double(b));
}

Value num_sub(Value a, Value b) {
    if (IS_INTEGER(a) && IS_INTEGER(b)) {
        if (IS_INT(a) && IS_INT(b)) return fixnum_sub(as_int(a), as_int(b));
        return big_sub(a, b);
    }
    if (is_exact(a) && is_exact(b)) {
        int64_t an, ad, bn, bd;
        to_rational(a, &an, &ad);
        to_rational(b, &bn, &bd);
        return rational_normalize(an * bd - bn * ad, ad * bd);
    }
    return make_double(as_number_double(a) - as_number_double(b));
}

Value num_mul(Value a, Value b) {
    if (IS_INTEGER(a) && IS_INTEGER(b)) {
        if (IS_INT(a) && IS_INT(b)) return fixnum_mul(as_int(a), as_int(b));
        return big_mul(a, b);
    }
    if (is_exact(a) && is_exact(b)) {
        int64_t an, ad, bn, bd;
        to_rational(a, &an, &ad);
        to_rational(b, &bn, &bd);
        return rational_normalize(an * bn, ad * bd);
    }
    return make_double(as_number_double(a) * as_number_double(b));
}

Value num_div(Value a, Value b) {
    /* Integer / integer that divides cleanly stays integer (matches Python /
     * Scheme semantics for `/` between exacts when the quotient is exact).
     * For bignum case we treat as exact division (truncating to integer when
     * quotient is exact, else fall back to double for now — secp256k1 ops
     * never use fractional bignum). */
    if (IS_INTEGER(a) && IS_INTEGER(b)) {
        if (big_is_zero(b)) lisp_error("division by zero");
        Value q = big_quotient(a, b);
        Value r = big_remainder(a, b);
        if (big_is_zero(r)) return q;
        /* Inexact fallback. */
        return make_double(as_number_double(a) / as_number_double(b));
    }
    if (is_exact(a) && is_exact(b)) {
        int64_t an, ad, bn, bd;
        to_rational(a, &an, &ad);
        to_rational(b, &bn, &bd);
        if (bn == 0) lisp_error("division by zero");
        return rational_normalize(an * bd, ad * bn);
    }
    double db = as_number_double(b);
    if (db == 0.0) lisp_error("division by zero");
    return make_double(as_number_double(a) / db);
}

Value num_neg(Value a) {
    if (IS_INT(a)) {
        int64_t v = as_int(a);
        return fixnum_sub(0, v);  /* handles -FIXNUM_MIN safely */
    }
    if (IS_BIGNUM(a)) return big_neg(a);
    if (IS_RATIONAL(a)) {
        Rational *r = AS_RATIONAL(a);
        return make_rational(-r->num, r->den);
    }
    return make_double(-as_double(a));
}

static int num_cmp(Value a, Value b) {
    if (IS_INTEGER(a) && IS_INTEGER(b)) return big_cmp(a, b);
    if (is_exact(a) && is_exact(b)) {
        int64_t an, ad, bn, bd;
        to_rational(a, &an, &ad);
        to_rational(b, &bn, &bd);
        int64_t lhs = an * bd;
        int64_t rhs = bn * ad;
        if (lhs < rhs) return -1;
        if (lhs > rhs) return 1;
        return 0;
    }
    double da = as_number_double(a), db = as_number_double(b);
    if (da < db) return -1;
    if (da > db) return 1;
    return 0;
}

bool num_eq(Value a, Value b) { return num_cmp(a, b) == 0; }
bool num_lt(Value a, Value b) { return num_cmp(a, b) < 0; }
bool num_gt(Value a, Value b) { return num_cmp(a, b) > 0; }
bool num_le(Value a, Value b) { return num_cmp(a, b) <= 0; }
bool num_ge(Value a, Value b) { return num_cmp(a, b) >= 0; }

/* ═══════════════════════════════════════════════════════════════════════════
 * Environment
 * ═══════════════════════════════════════════════════════════════════════════ */

#define ENV_INIT_BUCKETS 16

Env *make_env(Env *parent) {
    Env *e = (Env *)ul_malloc(sizeof(Env));
    e->hdr.type = OBJ_ENV;
    e->nbuckets = ENV_INIT_BUCKETS;
    e->count = 0;
    e->buckets = (EnvBinding **)ul_malloc(sizeof(EnvBinding *) * e->nbuckets);
    memset(e->buckets, 0, sizeof(EnvBinding *) * e->nbuckets);
    e->parent = parent;
    e->global = parent ? parent->global : NULL;
    return e;
}

static uint32_t env_hash_sym(Value sym, size_t nbuckets) {
    /* Symbol payload is a char* pointer — hash the pointer value for speed */
    return (uint32_t)((GET_PAYLOAD(sym) * 2654435761ULL) % nbuckets);
}

void env_define(Env *e, Value sym, Value val) {
    uint32_t h = env_hash_sym(sym, e->nbuckets);
    /* Check if already defined */
    EnvBinding *b = e->buckets[h];
    while (b) {
        if (b->sym == sym) { b->val = val; return; }
        b = b->next;
    }
    /* New binding */
    EnvBinding *nb = (EnvBinding *)ul_malloc_values(sizeof(EnvBinding));
    nb->sym = sym;
    nb->val = val;
    nb->next = e->buckets[h];
    e->buckets[h] = nb;
    e->count++;
    /* Resize if needed */
    if (e->count > e->nbuckets * 2) {
        size_t new_nb = e->nbuckets * 4;
        EnvBinding **new_bk = (EnvBinding **)ul_malloc(sizeof(EnvBinding *) * new_nb);
        memset(new_bk, 0, sizeof(EnvBinding *) * new_nb);
        for (size_t i = 0; i < e->nbuckets; i++) {
            EnvBinding *cur = e->buckets[i];
            while (cur) {
                EnvBinding *next = cur->next;
                uint32_t nh = env_hash_sym(cur->sym, new_nb);
                cur->next = new_bk[nh];
                new_bk[nh] = cur;
                cur = next;
            }
        }
        ul_free(e->buckets);
        e->buckets = new_bk;
        e->nbuckets = new_nb;
    }
}

static EnvBinding *env_find_local(Env *e, Value sym) {
    uint32_t h = env_hash_sym(sym, e->nbuckets);
    EnvBinding *b = e->buckets[h];
    while (b) {
        if (b->sym == sym) return b;
        b = b->next;
    }
    return NULL;
}

Value env_lookup(Env *e, Value sym) {
    /* Walk the lexical parent chain — covers locals, nested lets, and
     * the global env at the top. An earlier "global shortcut" jumped
     * straight to e->global after missing in the local frame, which
     * broke lexical scoping whenever a parent scope shadowed a global
     * (e.g. (let ((s 100)) (let ((m 0)) s)) with (define s 4) returned
     * 4 instead of 100). Walking parents end-to-end is correct and
     * still O(scope-depth) — bounded by code structure, not runtime. */
    Env *cur = e;
    while (cur) {
        EnvBinding *b = env_find_local(cur, sym);
        if (b) return b->val;
        cur = cur->parent;
    }
    lisp_error("undefined: %s", sym_name(sym));
    return VAL_NIL; /* unreachable */
}

bool env_set(Env *e, Value sym, Value val) {
    Env *cur = e;
    while (cur) {
        EnvBinding *b = env_find_local(cur, sym);
        if (b) { b->val = val; return true; }
        cur = cur->parent;
    }
    lisp_error("set! undefined: %s", sym_name(sym));
    return false;
}

Env *env_child(Env *parent, Value *params, int nparams, Value rest_param,
               Value *args, int nargs) {
    if (nargs < nparams) {
        lisp_error("arity: need %d, got %d", nparams, nargs);
    }
    if (IS_NIL(rest_param) && nargs > nparams) {
        lisp_error("arity: need %d, got %d", nparams, nargs);
    }
    Env *c = make_env(parent);
    for (int i = 0; i < nparams; i++) {
        env_define(c, params[i], args[i]);
    }
    if (!IS_NIL(rest_param)) {
        /* Build rest list */
        Value rest = VAL_NIL;
        for (int i = nargs - 1; i >= nparams; i--) {
            rest = cons(args[i], rest);
        }
        env_define(c, rest_param, rest);
    }
    return c;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Full continuation support
 * ═══════════════════════════════════════════════════════════════════════════ */

__thread ContTrampoline *g_cont_trampoline = NULL;
__thread FullCont *g_cont_invoked = NULL;
__thread Value g_cont_invoked_val = 0;

VMFrame *deep_copy_frames(VMFrame *frames, int nframes) {
    if (nframes == 0) return NULL;
    VMFrame *copy = (VMFrame *)ul_malloc(sizeof(VMFrame) * nframes);
    for (int i = 0; i < nframes; i++) {
        copy[i].instrs = frames[i].instrs;
        copy[i].ip = frames[i].ip;
        copy[i].n_instrs = frames[i].n_instrs;
        copy[i].env = deep_copy_env(frames[i].env);
        copy[i].stack_len = frames[i].stack_len;
        copy[i].stack_cap = frames[i].stack_cap;
        copy[i].stack = (Value *)ul_malloc_values(sizeof(Value) * copy[i].stack_cap);
        memcpy(copy[i].stack, frames[i].stack, sizeof(Value) * frames[i].stack_len);
    }
    return copy;
}

FullCont *make_full_cont(VMFrame *frames, int nframes, Value *stack, int stack_len,
                         int ip, Instruction *instrs, int n_instrs, Env *env, void *vm_id) {
    FullCont *c = (FullCont *)ul_malloc(sizeof(FullCont));
    c->hdr.type = OBJ_CONTINUATION;
    c->frames = deep_copy_frames(frames, nframes);
    c->nframes = nframes;
    c->stack = (Value *)ul_malloc_values(sizeof(Value) * (stack_len > 0 ? stack_len : 4));
    memcpy(c->stack, stack, sizeof(Value) * stack_len);
    c->stack_len = stack_len;
    c->ip = ip;
    c->instrs = instrs;
    c->n_instrs = n_instrs;
    c->env = deep_copy_env(env);
    c->vm_id = vm_id;
    return c;
}

/* Deep copy env chain (for multi-shot continuations) */
Env *deep_copy_env(Env *env) {
    if (!env) return NULL;
    if (env->global == env) return env; /* don't copy global */
    Env *ne = (Env *)ul_malloc(sizeof(Env));
    ne->hdr.type = OBJ_ENV;
    ne->nbuckets = env->nbuckets;
    ne->count = env->count;
    ne->buckets = (EnvBinding **)ul_malloc(sizeof(EnvBinding *) * ne->nbuckets);
    memset(ne->buckets, 0, sizeof(EnvBinding *) * ne->nbuckets);
    for (size_t i = 0; i < env->nbuckets; i++) {
        EnvBinding *src = env->buckets[i];
        EnvBinding **dst = &ne->buckets[i];
        while (src) {
            EnvBinding *nb = (EnvBinding *)ul_malloc_values(sizeof(EnvBinding));
            nb->sym = src->sym;
            nb->val = src->val;
            nb->next = NULL;
            *dst = nb;
            dst = &nb->next;
            src = src->next;
        }
    }
    ne->global = env->global;
    ne->parent = deep_copy_env(env->parent);
    return ne;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Procedure
 * ═══════════════════════════════════════════════════════════════════════════ */

Proc *make_proc(Value *params, int nparams, Value rest,
                ExprList body, Env *env, const char *name) {
    Proc *p = (Proc *)ul_malloc_values(sizeof(Proc));
    p->hdr.type = OBJ_PROC;
    p->params = (Value *)ul_malloc_values(sizeof(Value) * nparams);
    memcpy(p->params, params, sizeof(Value) * nparams);
    p->nparams = nparams;
    p->rest = rest;
    p->body.exprs = (Value *)ul_malloc_values(sizeof(Value) * body.count);
    memcpy(p->body.exprs, body.exprs, sizeof(Value) * body.count);
    p->body.count = body.count;
    p->env = env;
    p->name = name ? ul_strdup(name) : NULL;
    p->has_defs = has_internal_defines(body);
    p->jit_block = NULL;
    return p;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Macro
 * ═══════════════════════════════════════════════════════════════════════════ */

Value make_macro(Value transformer) {
    ULMacro *m = (ULMacro *)ul_malloc_values(sizeof(ULMacro));
    m->hdr.type = OBJ_MACRO;
    m->transformer = transformer;
    return VAL_PTR(m);
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Error object
 * ═══════════════════════════════════════════════════════════════════════════ */

Value make_error_object(const char *msg, Value *irritants, int nirr) {
    ErrorObject *e = (ErrorObject *)ul_malloc(sizeof(ErrorObject));
    e->hdr.type = OBJ_ERROR;
    e->message = ul_strdup(msg);
    e->nirritants = nirr;
    if (nirr > 0) {
        e->irritants = (Value *)ul_malloc_values(sizeof(Value) * nirr);
        memcpy(e->irritants, irritants, sizeof(Value) * nirr);
    } else {
        e->irritants = NULL;
    }
    return VAL_PTR(e);
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Port
 * ═══════════════════════════════════════════════════════════════════════════ */

Value make_file_port(FILE *fp, PortDir dir) {
    ULPort *p = (ULPort *)ul_malloc(sizeof(ULPort));
    p->hdr.type = OBJ_PORT;
    p->dir = dir;
    p->kind = PORT_FILE;
    p->fp = fp;
    p->str_buf = NULL;
    p->str_len = 0;
    p->str_pos = 0;
    p->str_cap = 0;
    p->closed = false;
    return VAL_PTR(p);
}

Value make_string_input_port(const char *s, size_t len) {
    ULPort *p = (ULPort *)ul_malloc(sizeof(ULPort));
    p->hdr.type = OBJ_PORT;
    p->dir = PORT_INPUT;
    p->kind = PORT_STRING;
    p->fp = NULL;
    p->str_buf = (char *)ul_malloc(len + 1);
    memcpy(p->str_buf, s, len);
    p->str_buf[len] = '\0';
    p->str_len = len;
    p->str_pos = 0;
    p->str_cap = len + 1;
    p->closed = false;
    return VAL_PTR(p);
}

Value make_string_output_port(void) {
    ULPort *p = (ULPort *)ul_malloc(sizeof(ULPort));
    p->hdr.type = OBJ_PORT;
    p->dir = PORT_OUTPUT;
    p->kind = PORT_STRING;
    p->fp = NULL;
    p->str_cap = 256;
    p->str_buf = (char *)ul_malloc(p->str_cap);
    p->str_buf[0] = '\0';
    p->str_len = 0;
    p->str_pos = 0;
    p->closed = false;
    return VAL_PTR(p);
}

void port_write_char(ULPort *p, int ch) {
    if (p->kind == PORT_FILE) {
        fputc(ch, p->fp);
    } else {
        if (p->str_len + 1 >= p->str_cap) {
            p->str_cap *= 2;
            p->str_buf = (char *)ul_realloc(p->str_buf, p->str_cap);
        }
        p->str_buf[p->str_len++] = (char)ch;
        p->str_buf[p->str_len] = '\0';
    }
}

void port_write_str(ULPort *p, const char *s, size_t len) {
    if (p->kind == PORT_FILE) {
        fwrite(s, 1, len, p->fp);
    } else {
        /* Grow with 2x doubling on small buffers (fast amortization), shift
         * to 1.5x once we cross 256 MB. 2x at that scale creates a 3x peak
         * during realloc (old + new) — 8GB → 16GB needs 24GB transient,
         * OOMs any reasonable VM. 1.5x bounds peak at 2.5x. */
        while (p->str_len + len + 1 > p->str_cap) {
            if (p->str_cap < (1ULL << 28)) {
                p->str_cap *= 2;
            } else {
                p->str_cap += p->str_cap / 2;
            }
            p->str_buf = (char *)ul_realloc(p->str_buf, p->str_cap);
        }
        memcpy(p->str_buf + p->str_len, s, len);
        p->str_len += len;
        p->str_buf[p->str_len] = '\0';
    }
}

int port_read_char(ULPort *p) {
    if (p->kind == PORT_FILE) {
        return fgetc(p->fp);
    } else {
        if (p->str_pos >= p->str_len) return EOF;
        return (unsigned char)p->str_buf[p->str_pos++];
    }
}

int port_peek_char(ULPort *p) {
    if (p->kind == PORT_FILE) {
        int c = fgetc(p->fp);
        if (c != EOF) ungetc(c, p->fp);
        return c;
    } else {
        if (p->str_pos >= p->str_len) return EOF;
        return (unsigned char)p->str_buf[p->str_pos];
    }
}

char *port_read_line(ULPort *p) {
    if (p->kind == PORT_FILE) {
        char buf[4096];
        if (!fgets(buf, sizeof(buf), p->fp)) return NULL;
        size_t len = strlen(buf);
        if (len > 0 && buf[len-1] == '\n') buf[--len] = '\0';
        return ul_strdup(buf);
    } else {
        if (p->str_pos >= p->str_len) return NULL;
        size_t start = p->str_pos;
        while (p->str_pos < p->str_len && p->str_buf[p->str_pos] != '\n')
            p->str_pos++;
        size_t len = p->str_pos - start;
        if (p->str_pos < p->str_len) p->str_pos++; /* skip \n */
        char *result = (char *)ul_malloc(len + 1);
        memcpy(result, p->str_buf + start, len);
        result[len] = '\0';
        return result;
    }
}

char *port_get_output_string(ULPort *p) {
    if (p->kind != PORT_STRING || p->dir != PORT_OUTPUT) return ul_strdup("");
    char *result = (char *)ul_malloc(p->str_len + 1);
    memcpy(result, p->str_buf, p->str_len);
    result[p->str_len] = '\0';
    return result;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Record type registry
 * ═══════════════════════════════════════════════════════════════════════════ */

RecordType *g_record_types = NULL;

RecordType *find_record_type(const char *name) {
    RecordType *rt = g_record_types;
    while (rt) {
        if (strcmp(rt->name, name) == 0) return rt;
        rt = rt->next;
    }
    return NULL;
}

void register_record_type(const char *name, char **fields, int nfields, const char *parent) {
    RecordType *rt = (RecordType *)ul_malloc(sizeof(RecordType));
    rt->name = ul_strdup(name);
    rt->fields = (char **)ul_malloc(sizeof(char *) * nfields);
    for (int i = 0; i < nfields; i++) rt->fields[i] = ul_strdup(fields[i]);
    rt->nfields = nfields;
    rt->parent = parent ? ul_strdup(parent) : NULL;
    rt->next = g_record_types;
    g_record_types = rt;
}

bool is_subtype(const char *child, const char *ancestor) {
    if (strcmp(child, ancestor) == 0) return true;
    RecordType *rt = find_record_type(child);
    while (rt && rt->parent) {
        if (strcmp(rt->parent, ancestor) == 0) return true;
        rt = find_record_type(rt->parent);
    }
    return false;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Module registry
 * ═══════════════════════════════════════════════════════════════════════════ */

Module *g_modules = NULL;

/* ═══════════════════════════════════════════════════════════════════════════
 * Common interned symbols
 * ═══════════════════════════════════════════════════════════════════════════ */

Value SYM_QUOTE, SYM_IF, SYM_COND, SYM_AND, SYM_OR;
Value SYM_WHEN, SYM_UNLESS, SYM_BEGIN, SYM_DEFINE, SYM_SET;
Value SYM_LAMBDA, SYM_LAMBDA_UC, SYM_LET, SYM_LET_STAR, SYM_LETREC;
Value SYM_LETREC_STAR, SYM_DO, SYM_QUASIQUOTE, SYM_UNQUOTE;
Value SYM_UNQUOTE_SPLICING, SYM_DEFINE_MACRO, SYM_DEFMACRO;
Value SYM_DEFINE_SYNTAX, SYM_LET_SYNTAX, SYM_LETREC_SYNTAX;
Value SYM_SYNTAX_RULES, SYM_VALUES, SYM_CALL_WITH_VALUES;
Value SYM_CALL_CC, SYM_CALL_CC2, SYM_APPLY, SYM_EVAL;
Value SYM_ERROR, SYM_DEFINE_RECORD_TYPE, SYM_MODULE, SYM_IMPORT;
Value SYM_LOAD, SYM_INCLUDE, SYM_PARAMETERIZE, SYM_DYNAMIC_WIND;
Value SYM_WITH_EXCEPTION_HANDLER, SYM_GUARD, SYM_DEFINE_VALUES;
Value SYM_LET_VALUES, SYM_LET_STAR_VALUES, SYM_CASE;
Value SYM_ELSE, SYM_ARROW, SYM_DOT, SYM_ELLIPSIS, SYM_UNDERSCORE;
Value SYM_EXPORT;

void init_symbols(void) {
    SYM_QUOTE = intern("quote");
    SYM_IF = intern("if");
    SYM_COND = intern("cond");
    SYM_AND = intern("and");
    SYM_OR = intern("or");
    SYM_WHEN = intern("when");
    SYM_UNLESS = intern("unless");
    SYM_BEGIN = intern("begin");
    SYM_DEFINE = intern("define");
    SYM_SET = intern("set!");
    SYM_LAMBDA = intern("lambda");
    SYM_LAMBDA_UC = intern("λ");
    SYM_LET = intern("let");
    SYM_LET_STAR = intern("let*");
    SYM_LETREC = intern("letrec");
    SYM_LETREC_STAR = intern("letrec*");
    SYM_DO = intern("do");
    SYM_QUASIQUOTE = intern("quasiquote");
    SYM_UNQUOTE = intern("unquote");
    SYM_UNQUOTE_SPLICING = intern("unquote-splicing");
    SYM_DEFINE_MACRO = intern("define-macro");
    SYM_DEFMACRO = intern("defmacro");
    SYM_DEFINE_SYNTAX = intern("define-syntax");
    SYM_LET_SYNTAX = intern("let-syntax");
    SYM_LETREC_SYNTAX = intern("letrec-syntax");
    SYM_SYNTAX_RULES = intern("syntax-rules");
    SYM_VALUES = intern("values");
    SYM_CALL_WITH_VALUES = intern("call-with-values");
    SYM_CALL_CC = intern("call/cc");
    SYM_CALL_CC2 = intern("call-with-current-continuation");
    SYM_APPLY = intern("apply");
    SYM_EVAL = intern("eval");
    SYM_ERROR = intern("error");
    SYM_DEFINE_RECORD_TYPE = intern("define-record-type");
    SYM_MODULE = intern("module");
    SYM_IMPORT = intern("import");
    SYM_LOAD = intern("load");
    SYM_INCLUDE = intern("include");
    SYM_PARAMETERIZE = intern("parameterize");
    SYM_DYNAMIC_WIND = intern("dynamic-wind");
    SYM_WITH_EXCEPTION_HANDLER = intern("with-exception-handler");
    SYM_GUARD = intern("guard");
    SYM_DEFINE_VALUES = intern("define-values");
    SYM_LET_VALUES = intern("let-values");
    SYM_LET_STAR_VALUES = intern("let*-values");
    SYM_CASE = intern("case");
    SYM_ELSE = intern("else");
    SYM_ARROW = intern("=>");
    SYM_DOT = intern(".");
    SYM_ELLIPSIS = intern("...");
    SYM_UNDERSCORE = intern("_");
    SYM_EXPORT = intern("export");
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Global state
 * ═══════════════════════════════════════════════════════════════════════════ */

bool g_auto_compile = false;
