/*
 * SLOP Runtime - Minimal runtime for SLOP-generated C code
 * 
 * Provides:
 * - Arena allocator
 * - String type
 * - List type
 * - Map type
 * - Contract macros
 */

#ifndef SLOP_RUNTIME_H
#define SLOP_RUNTIME_H

#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdatomic.h>

#ifdef SLOP_INTERN_THREADSAFE
#include <pthread.h>
#endif

/* ============================================================
 * Configuration
 * ============================================================ */

#ifndef SLOP_ARENA_DEFAULT_SIZE
#define SLOP_ARENA_DEFAULT_SIZE 4096
#endif

#ifndef SLOP_MAP_INITIAL_CAPACITY
#define SLOP_MAP_INITIAL_CAPACITY 16
#endif

#ifndef SLOP_ARENA_MAX_TOTAL_BYTES
#define SLOP_ARENA_MAX_TOTAL_BYTES (256UL * 1024UL * 1024UL)  /* 256 MB */
#endif

/* Global allocation tracking across all arenas (atomic for thread safety).
 * Weak attribute ensures linker merges all TU definitions into one symbol. */
_Atomic size_t slop_global_allocated __attribute__((weak)) = 0;

/* ============================================================
 * Contracts
 * ============================================================ */

#ifdef SLOP_DEBUG
    #define SLOP_PRE(cond, msg) \
        do { if (!(cond)) { \
            fprintf(stderr, "SLOP precondition failed: %s\n  at %s:%d\n", \
                    msg, __FILE__, __LINE__); \
            abort(); \
        }} while(0)
    
    #define SLOP_POST(cond, msg) \
        do { if (!(cond)) { \
            fprintf(stderr, "SLOP postcondition failed: %s\n  at %s:%d\n", \
                    msg, __FILE__, __LINE__); \
            abort(); \
        }} while(0)
    
    #define SLOP_ASSERT(cond, msg) \
        do { if (!(cond)) { \
            fprintf(stderr, "SLOP assertion failed: %s\n  at %s:%d\n", \
                    msg, __FILE__, __LINE__); \
            abort(); \
        }} while(0)
#else
    #define SLOP_PRE(cond, msg) ((void)0)
    #define SLOP_POST(cond, msg) ((void)0)
    #define SLOP_ASSERT(cond, msg) ((void)0)
#endif

/* Expression-form range check - returns value after checking bounds.
 * Unlike SLOP_PRE, this can be used in expression contexts like return statements. */
#ifdef SLOP_DEBUG
    #define SLOP_RANGE_CHECK(val, cond, msg) \
        ({ __auto_type _v = (val); \
           if (!(cond)) { \
               fprintf(stderr, "SLOP range check failed: %s\n  at %s:%d\n", \
                       msg, __FILE__, __LINE__); \
               abort(); \
           } \
           _v; })
#else
    #define SLOP_RANGE_CHECK(val, cond, msg) (val)
#endif

/* ============================================================
 * Arena Allocator
 * ============================================================ */

typedef struct slop_arena {
    uint8_t* base;
    size_t offset;
    size_t capacity;
    size_t total_allocated;   /* Total bytes across all arenas in chain */
    struct slop_arena* next;  /* For overflow arenas */
} slop_arena;

/* ============================================================
 * String Interning Pool
 * ============================================================ */

#ifndef SLOP_INTERN_BUCKET_COUNT
#define SLOP_INTERN_BUCKET_COUNT 4096
#endif

/* Forward declare slop_string for intern pool */
struct slop_string_fwd;

typedef struct slop_intern_entry {
    uint64_t hash;
    size_t len;
    const char* data;
    struct slop_intern_entry* next;
} slop_intern_entry;

typedef struct {
    slop_intern_entry** buckets;
    size_t bucket_count;
    size_t entry_count;
    slop_arena* arena;  /* Pool owns its own arena */
#ifdef SLOP_INTERN_THREADSAFE
    pthread_mutex_t lock;
#endif
} slop_intern_pool;

static slop_intern_pool* slop_global_intern_pool = NULL;

static inline slop_arena slop_arena_new(size_t capacity) {
    /* Check global cap BEFORE allocating (atomic load for thread safety) */
#ifndef SLOP_ARENA_NO_CAP
    if (atomic_load(&slop_global_allocated) + capacity > SLOP_ARENA_MAX_TOTAL_BYTES) {
        fprintf(stderr, "SLOP: arena allocation cap exceeded (%zu bytes). "
                "Increase SLOP_ARENA_MAX_TOTAL_BYTES or reduce allocation.\n",
                (size_t)SLOP_ARENA_MAX_TOTAL_BYTES);
        abort();
    }
#endif

    slop_arena arena;
    arena.base = (uint8_t*)malloc(capacity);
    arena.offset = 0;
    arena.capacity = (arena.base != NULL) ? capacity : 0;
    arena.total_allocated = arena.capacity;  /* Keep for per-chain tracking */
    arena.next = NULL;

    if (arena.base != NULL) {
        atomic_fetch_add(&slop_global_allocated, capacity);  /* Track globally */
    }
    return arena;
}

static inline void* slop_arena_alloc(slop_arena* arena, size_t size) {
    /* Handle failed arena */
    if (arena->base == NULL) return NULL;

    /* Align to 8 bytes */
    size = (size + 7) & ~7;

    /* Check if we need overflow arena */
    if (arena->offset + size > arena->capacity) {
        if (arena->next == NULL) {
            size_t new_cap = arena->capacity * 2;
            if (new_cap < size) new_cap = size * 2;

            /* Check GLOBAL cap before allocating overflow (atomic load for thread safety) */
#ifndef SLOP_ARENA_NO_CAP
            if (atomic_load(&slop_global_allocated) + new_cap > SLOP_ARENA_MAX_TOTAL_BYTES) {
                fprintf(stderr, "SLOP: arena allocation cap exceeded (%zu bytes). "
                        "Increase SLOP_ARENA_MAX_TOTAL_BYTES or reduce allocation.\n",
                        (size_t)SLOP_ARENA_MAX_TOTAL_BYTES);
                abort();
            }
#endif

            arena->next = (slop_arena*)malloc(sizeof(slop_arena));
            if (arena->next == NULL) {
                fprintf(stderr, "SLOP: arena overflow malloc failed (requested %zu bytes).\n", sizeof(slop_arena));
                abort();
            }

            /* Create overflow arena (slop_arena_new will increment global counter) */
            *arena->next = slop_arena_new(new_cap);
            if (arena->next->base == NULL) {
                fprintf(stderr, "SLOP: arena block malloc failed (requested %zu bytes).\n", new_cap);
                free(arena->next);
                arena->next = NULL;
                abort();
            }

            /* Propagate per-chain total */
            arena->next->total_allocated = arena->total_allocated + new_cap;
        }
        return slop_arena_alloc(arena->next, size);
    }

    void* ptr = arena->base + arena->offset;
    arena->offset += size;
    return ptr;
}

static inline void slop_arena_free(slop_arena* arena) {
    if (arena->next) {
        slop_arena_free(arena->next);
        free(arena->next);
        arena->next = NULL;
    }

    /* Decrement global counter (atomic for thread safety) */
    if (arena->base != NULL && arena->capacity > 0) {
        atomic_fetch_sub(&slop_global_allocated, arena->capacity);
    }

    free(arena->base);
    arena->base = NULL;
    arena->offset = 0;
    arena->capacity = 0;
    arena->total_allocated = 0;
}

static inline void slop_arena_reset(slop_arena* arena) {
    arena->offset = 0;
    if (arena->next) {
        slop_arena_free(arena->next);
        free(arena->next);
        arena->next = NULL;
    }
    arena->total_allocated = arena->capacity;
}

/* ============================================================
 * String Type (immutable, length-prefixed)
 * ============================================================ */

typedef struct {
    size_t len;
    const char* data;
} slop_string;

#define SLOP_STR(literal) ((slop_string){sizeof(literal)-1, literal})

/* String constructor macro for native transpiler: String(data, len) -> slop_string */
#define String(data, len) ((slop_string){(len), (const char*)(data)})

/* Generic Option constructors for native transpiler */
/* These use anonymous struct literals compatible with slop_option types */
typedef struct { bool has_value; int64_t value; } _slop_option_generic;
#define some(v) ((_slop_option_generic){true, (int64_t)(v)})
#define none ((_slop_option_generic){false, 0})
/* Generic unwrap for any Option type */
#define unwrap(opt) ({ __auto_type _opt = (opt); SLOP_PRE(_opt.has_value, "unwrap on None"); _opt.value; })

/* Generic Result type for native transpiler */
/* ok/error constructors produce generic result that can be assigned to typed results */
typedef struct { bool is_ok; union { int64_t ok; int64_t err; } data; } _slop_result_generic;
#define ok(v) ((_slop_result_generic){true, {.ok = (int64_t)(v)}})
#define error(e) ((_slop_result_generic){false, {.err = (int64_t)(e)}})

/* Closure type for lambda expressions with captured variables */
typedef struct { void* fn; void* env; } slop_closure_t;

/* ============================================================
 * String Interning Functions
 * ============================================================ */

/* FNV-1a hash for raw string data (used by interning) */
static inline uint64_t slop_hash_string_data(const char* str, size_t len) {
    uint64_t hash = 14695981039346656037ULL;
    for (size_t i = 0; i < len; i++) {
        hash ^= (uint8_t)str[i];
        hash *= 1099511628211ULL;
    }
    return hash;
}

/* Initialize the global intern pool (called lazily) */
#ifdef SLOP_INTERN_THREADSAFE
static pthread_once_t slop_intern_once = PTHREAD_ONCE_INIT;
static void slop_intern_pool_init_impl(void) {
    slop_global_intern_pool = (slop_intern_pool*)malloc(sizeof(slop_intern_pool));
    slop_global_intern_pool->bucket_count = SLOP_INTERN_BUCKET_COUNT;
    slop_global_intern_pool->entry_count = 0;
    slop_global_intern_pool->buckets = (slop_intern_entry**)calloc(
        SLOP_INTERN_BUCKET_COUNT, sizeof(slop_intern_entry*));
    slop_global_intern_pool->arena = (slop_arena*)malloc(sizeof(slop_arena));
    *slop_global_intern_pool->arena = slop_arena_new(1024 * 1024);
    pthread_mutex_init(&slop_global_intern_pool->lock, NULL);
}
static inline void slop_intern_pool_init(void) {
    pthread_once(&slop_intern_once, slop_intern_pool_init_impl);
}
#else
static inline void slop_intern_pool_init(void) {
    if (slop_global_intern_pool) return;

    slop_global_intern_pool = (slop_intern_pool*)malloc(sizeof(slop_intern_pool));
    slop_global_intern_pool->bucket_count = SLOP_INTERN_BUCKET_COUNT;
    slop_global_intern_pool->entry_count = 0;
    slop_global_intern_pool->buckets = (slop_intern_entry**)calloc(
        SLOP_INTERN_BUCKET_COUNT, sizeof(slop_intern_entry*));

    /* Create dedicated arena for interned strings */
    slop_global_intern_pool->arena = (slop_arena*)malloc(sizeof(slop_arena));
    *slop_global_intern_pool->arena = slop_arena_new(1024 * 1024);  /* 1MB initial */
}
#endif

/* Intern a string - returns existing string if already interned, otherwise allocates new */
static inline slop_string slop_intern_string(const char* str, size_t len) {
    slop_intern_pool_init();

    uint64_t hash = slop_hash_string_data(str, len);
    size_t bucket = hash % slop_global_intern_pool->bucket_count;

#ifdef SLOP_INTERN_THREADSAFE
    pthread_mutex_lock(&slop_global_intern_pool->lock);
#endif

    /* Check existing entries */
    slop_intern_entry* entry = slop_global_intern_pool->buckets[bucket];
    while (entry) {
        if (entry->hash == hash &&
            entry->len == len &&
            memcmp(entry->data, str, len) == 0) {
            /* Found existing - return it */
#ifdef SLOP_INTERN_THREADSAFE
            pthread_mutex_unlock(&slop_global_intern_pool->lock);
#endif
            return (slop_string){entry->len, entry->data};
        }
        entry = entry->next;
    }

    /* Allocate new interned string in pool's arena */
    char* data = (char*)slop_arena_alloc(slop_global_intern_pool->arena, len + 1);
    memcpy(data, str, len);
    data[len] = '\0';

    /* Add entry to pool */
    slop_intern_entry* new_entry = (slop_intern_entry*)slop_arena_alloc(
        slop_global_intern_pool->arena, sizeof(slop_intern_entry));
    new_entry->hash = hash;
    new_entry->len = len;
    new_entry->data = data;
    new_entry->next = slop_global_intern_pool->buckets[bucket];
    slop_global_intern_pool->buckets[bucket] = new_entry;
    slop_global_intern_pool->entry_count++;

#ifdef SLOP_INTERN_THREADSAFE
    pthread_mutex_unlock(&slop_global_intern_pool->lock);
#endif
    return (slop_string){len, data};
}

/* Intern a null-terminated string */
static inline slop_string slop_intern_cstring(const char* str) {
    return slop_intern_string(str, strlen(str));
}

/* Interning version of string_new - use for strings that will be compared often */
static inline slop_string slop_string_intern(slop_arena* arena, const char* cstr) {
    (void)arena;  /* Interned strings use global pool, not passed arena */
    return slop_intern_cstring(cstr);
}

/* ============================================================
 * String Construction Functions
 * ============================================================ */

/* String interning mode - set to 1 to enable global interning for all strings */
#ifndef SLOP_STRING_INTERN_ALL
#define SLOP_STRING_INTERN_ALL 1
#endif

static inline slop_string slop_string_new(slop_arena* arena, const char* cstr) {
#if SLOP_STRING_INTERN_ALL
    (void)arena;  /* Interned strings use global pool */
    return slop_intern_cstring(cstr);
#else
    size_t len = strlen(cstr);
    char* data = (char*)slop_arena_alloc(arena, len + 1);
    memcpy(data, cstr, len + 1);
    return (slop_string){len, data};
#endif
}

static inline slop_string string_new(slop_arena* arena, const char* cstr) {
    return slop_string_new(arena, cstr);
}

static inline slop_string slop_string_new_len(slop_arena* arena, const char* src, size_t len) {
#if SLOP_STRING_INTERN_ALL
    (void)arena;  /* Interned strings use global pool */
    return slop_intern_string(src, len);
#else
    char* data = (char*)slop_arena_alloc(arena, len + 1);
    memcpy(data, src, len);
    data[len] = '\0';
    return (slop_string){len, data};
#endif
}

static inline bool slop_string_eq(slop_string a, slop_string b) {
    if (a.len != b.len) return false;
    return memcmp(a.data, b.data, a.len) == 0;
}

static inline slop_string slop_string_concat(slop_arena* arena, slop_string a, slop_string b) {
    size_t len = a.len + b.len;
    char* data = (char*)slop_arena_alloc(arena, len + 1);
    memcpy(data, a.data, a.len);
    memcpy(data + a.len, b.data, b.len);
    data[len] = '\0';
    return (slop_string){len, data};
}

static inline slop_string slop_string_push_char(slop_arena* arena, slop_string s, uint8_t c) {
    size_t len = s.len + 1;
    char* data = (char*)slop_arena_alloc(arena, len + 1);
    memcpy(data, s.data, s.len);
    data[s.len] = (char)c;
    data[len] = '\0';
    return (slop_string){len, data};
}

static inline slop_string slop_string_slice(slop_string s, size_t start, size_t end) {
    SLOP_PRE(start <= end && end <= s.len, "valid slice bounds");
    return (slop_string){end - start, s.data + start};
}

static inline size_t string_len(slop_string s) {
    return s.len;
}

static inline int32_t string_char_at(slop_string s, size_t index) {
    if (index >= s.len) return 0;
    return (int32_t)(unsigned char)s.data[index];
}

static inline slop_string string_slice(slop_string s, size_t start, size_t end) {
    return slop_string_slice(s, start, end);
}

static inline int64_t string_to_int(slop_string s) {
    int64_t result = 0;
    int negative = 0;
    size_t i = 0;
    if (s.len > 0 && s.data[0] == '-') {
        negative = 1;
        i = 1;
    }
    for (; i < s.len; i++) {
        if (s.data[i] >= '0' && s.data[i] <= '9') {
            result = result * 10 + (s.data[i] - '0');
        }
    }
    return negative ? -result : result;
}

static inline slop_string int_to_string(slop_arena* arena, int64_t n) {
    char buf[32];
    int len = snprintf(buf, sizeof(buf), "%ld", (long)n);
    return slop_string_new_len(arena, buf, len);
}

static inline slop_string string_concat(slop_arena* arena, slop_string a, slop_string b) {
    return slop_string_concat(arena, a, b);
}

static inline bool string_eq(slop_string a, slop_string b) {
    return slop_string_eq(a, b);
}

/* ============================================================
 * Bytes Type (mutable, length + capacity)
 * ============================================================ */

typedef struct {
    size_t len;
    size_t cap;
    uint8_t* data;
} slop_bytes;

static inline slop_bytes slop_bytes_new(slop_arena* arena, size_t capacity) {
    uint8_t* data = (uint8_t*)slop_arena_alloc(arena, capacity);
    return (slop_bytes){0, capacity, data};
}

static inline slop_bytes slop_bytes_from(slop_arena* arena, const uint8_t* src, size_t len) {
    uint8_t* data = (uint8_t*)slop_arena_alloc(arena, len);
    memcpy(data, src, len);
    return (slop_bytes){len, len, data};
}

/* ============================================================
 * List Type (dynamic array, generic via macros)
 * ============================================================ */

/* SLOP_LIST_DECLARE: struct only — safe with incomplete element types (uses T*) */
#define SLOP_LIST_DECLARE(T, Name) \
    typedef struct { \
        size_t len; \
        size_t cap; \
        T* data; \
    } Name;

/* SLOP_LIST_IMPL: inline functions — requires sizeof(T), so T must be complete */
#define SLOP_LIST_IMPL(T, Name) \
    static inline Name Name##_new(slop_arena* arena, size_t initial_cap) { \
        T* data = (T*)slop_arena_alloc(arena, initial_cap * sizeof(T)); \
        return (Name){0, initial_cap, data}; \
    } \
    \
    static inline void Name##_push(slop_arena* arena, Name* list, T item) { \
        if (list->len >= list->cap) { \
            size_t new_cap = list->cap * 2; \
            T* new_data = (T*)slop_arena_alloc(arena, new_cap * sizeof(T)); \
            memcpy(new_data, list->data, list->len * sizeof(T)); \
            list->data = new_data; \
            list->cap = new_cap; \
        } \
        list->data[list->len++] = item; \
    } \
    \
    static inline T* Name##_get(Name* list, size_t i) { \
        SLOP_PRE(i < list->len, "list index in bounds"); \
        return &list->data[i]; \
    }

/* SLOP_LIST_DEFINE: combined declare + impl (original convenience macro) */
#define SLOP_LIST_DEFINE(T, Name) SLOP_LIST_DECLARE(T, Name) SLOP_LIST_IMPL(T, Name)

/* Pre-define common list types */
SLOP_LIST_DEFINE(int64_t, slop_list_int)
SLOP_LIST_DEFINE(double, slop_list_float)
SLOP_LIST_DEFINE(slop_string, slop_list_string)
SLOP_LIST_DEFINE(void*, slop_list_ptr)

/* String split - splits string on single-char delimiter */
static inline slop_list_string string_split(slop_arena* arena, slop_string s, slop_string delim) {
    SLOP_PRE(delim.len == 1, "delimiter must be single character");
    char dc = delim.data[0];

    /* Count segments */
    size_t count = 1;
    for (size_t i = 0; i < s.len; i++) {
        if (s.data[i] == dc) count++;
    }

    slop_list_string result = slop_list_string_new(arena, count);

    size_t start = 0;
    for (size_t i = 0; i <= s.len; i++) {
        if (i == s.len || s.data[i] == dc) {
            slop_string seg = slop_string_new_len(arena, s.data + start, i - start);
            slop_list_string_push(arena, &result, seg);
            start = i + 1;
        }
    }
    return result;
}

/* ============================================================
 * Map Type (generic hash map with arbitrary key types)
 * ============================================================ */

/* Function pointer types for hash and equality */
typedef uint64_t (*slop_hash_fn)(const void* key);
typedef bool (*slop_eq_fn)(const void* a, const void* b);

typedef struct {
    void* key;          /* Pointer to key (arena-allocated copy) */
    void* value;
    bool occupied;
} slop_map_entry;

typedef struct {
    size_t len;
    size_t cap;
    size_t key_size;    /* Size of key type in bytes */
    slop_hash_fn hash;  /* Hash function for keys */
    slop_eq_fn eq;      /* Equality function for keys */
    slop_map_entry* entries;
} slop_map;

/* ============================================================
 * Hash functions for primitive types
 * ============================================================ */

/* FNV-1a hash for strings */
static inline uint64_t slop_hash_string(const void* key) {
    const slop_string* s = (const slop_string*)key;
    uint64_t hash = 14695981039346656037ULL;
    for (size_t i = 0; i < s->len; i++) {
        hash ^= (uint8_t)s->data[i];
        hash *= 1099511628211ULL;
    }
    return hash;
}

static inline bool slop_eq_string(const void* a, const void* b) {
    return slop_string_eq(*(const slop_string*)a, *(const slop_string*)b);
}

/* Hash for integers (uses splitmix64) */
static inline uint64_t slop_hash_int(const void* key) {
    uint64_t x = *(const int64_t*)key;
    x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
    x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
    return x ^ (x >> 31);
}

static inline bool slop_eq_int(const void* a, const void* b) {
    return *(const int64_t*)a == *(const int64_t*)b;
}

/* Hash for unsigned integers */
static inline uint64_t slop_hash_uint(const void* key) {
    uint64_t x = *(const uint64_t*)key;
    x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
    x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
    return x ^ (x >> 31);
}

static inline bool slop_eq_uint(const void* a, const void* b) {
    return *(const uint64_t*)a == *(const uint64_t*)b;
}

/* Hash for pointers (useful for identity maps) */
static inline uint64_t slop_hash_ptr(const void* key) {
    uint64_t x = (uint64_t)(*(const void**)key);
    x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
    x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
    return x ^ (x >> 31);
}

static inline bool slop_eq_ptr(const void* a, const void* b) {
    return *(const void**)a == *(const void**)b;
}

/* Hash for symbols (which are just integers internally) */
#define slop_hash_symbol slop_hash_int
#define slop_eq_symbol slop_eq_int

/* Generate hash/eq functions for arbitrary struct types using FNV-1a */
#define SLOP_STRUCT_HASH_EQ_DEFINE(T) \
    static inline uint64_t slop_hash_##T(const void* key) { \
        const uint8_t* bytes = (const uint8_t*)key; \
        uint64_t hash = 14695981039346656037ULL; \
        for (size_t i = 0; i < sizeof(T); i++) { \
            hash ^= bytes[i]; \
            hash *= 1099511628211ULL; \
        } \
        return hash; \
    } \
    static inline bool slop_eq_##T(const void* a, const void* b) { \
        return memcmp(a, b, sizeof(T)) == 0; \
    }

/* ============================================================
 * Map operations
 * ============================================================ */

static inline slop_map slop_map_new(slop_arena* arena, size_t capacity,
                                     size_t key_size, slop_hash_fn hash, slop_eq_fn eq) {
    slop_map_entry* entries = (slop_map_entry*)slop_arena_alloc(
        arena, capacity * sizeof(slop_map_entry));
    memset(entries, 0, capacity * sizeof(slop_map_entry));
    return (slop_map){0, capacity, key_size, hash, eq, entries};
}

/* Return pointer to arena-allocated map (for slop_map* type) */
static inline slop_map* slop_map_new_ptr(slop_arena* arena, size_t capacity,
                                          size_t key_size, slop_hash_fn hash, slop_eq_fn eq) {
    slop_map* map = (slop_map*)slop_arena_alloc(arena, sizeof(slop_map));
    *map = slop_map_new(arena, capacity, key_size, hash, eq);
    return map;
}

/* Legacy string-only constructor for backward compatibility */
static inline slop_map* slop_map_new_string(slop_arena* arena, size_t capacity) {
    return slop_map_new_ptr(arena, capacity, sizeof(slop_string), slop_hash_string, slop_eq_string);
}

static inline void* slop_map_get(slop_map* map, const void* key) {
    uint64_t hash = map->hash(key);
    size_t idx = hash % map->cap;

    for (size_t i = 0; i < map->cap; i++) {
        size_t probe = (idx + i) % map->cap;
        if (!map->entries[probe].occupied) {
            return NULL;
        }
        if (map->eq(map->entries[probe].key, key)) {
            return map->entries[probe].value;
        }
    }
    return NULL;
}

/* Forward declaration for recursive call in put */
static inline void slop_map_put(slop_arena* arena, slop_map* map,
                                 const void* key, void* value);

static inline void slop_map_grow(slop_arena* arena, slop_map* map) {
    size_t new_cap = map->cap * 2;
    slop_map new_map = slop_map_new(arena, new_cap, map->key_size, map->hash, map->eq);
    for (size_t i = 0; i < map->cap; i++) {
        if (map->entries[i].occupied) {
            slop_map_put(arena, &new_map,
                        map->entries[i].key, map->entries[i].value);
        }
    }
    *map = new_map;
}

static inline void slop_map_put(slop_arena* arena, slop_map* map,
                                 const void* key, void* value) {
    /* Grow if needed (75% load factor) */
    if (map->len * 4 >= map->cap * 3) {
        slop_map_grow(arena, map);
    }

    uint64_t hash = map->hash(key);
    size_t idx = hash % map->cap;

    for (size_t i = 0; i < map->cap; i++) {
        size_t probe = (idx + i) % map->cap;
        if (!map->entries[probe].occupied) {
            /* Allocate and copy key */
            void* key_copy = slop_arena_alloc(arena, map->key_size);
            memcpy(key_copy, key, map->key_size);
            map->entries[probe].key = key_copy;
            map->entries[probe].value = value;
            map->entries[probe].occupied = true;
            map->len++;
            return;
        }
        if (map->eq(map->entries[probe].key, key)) {
            map->entries[probe].value = value;
            return;
        }
    }
}

static inline bool slop_map_has(slop_map* map, const void* key) {
    return slop_map_get(map, key) != NULL;
}

static inline bool slop_map_remove(slop_map* map, const void* key) {
    uint64_t hash = map->hash(key);
    size_t idx = hash % map->cap;
    for (size_t i = 0; i < map->cap; i++) {
        size_t probe = (idx + i) % map->cap;
        if (!map->entries[probe].occupied) return false;
        if (map->eq(map->entries[probe].key, key)) {
            map->entries[probe].occupied = false;
            map->len--;
            return true;
        }
    }
    return false;
}

/* Get all keys - returns generic list of pointers to keys */
static inline size_t slop_map_key_count(slop_map* map) {
    return map->len;
}

/* Legacy string key iteration for backward compatibility */
static inline slop_list_string slop_map_keys(slop_arena* arena, slop_map* map) {
    slop_list_string result = slop_list_string_new(arena, map->len > 0 ? map->len : 1);
    for (size_t i = 0; i < map->cap; i++) {
        if (map->entries[i].occupied) {
            slop_list_string_push(arena, &result, *(slop_string*)map->entries[i].key);
        }
    }
    return result;
}

/* Generic set elements - returns raw pointer to arena-allocated array of keys
 * The caller should cast the data pointer to the appropriate element type.
 * Returns a struct with data pointer, len, and cap (list-compatible layout). */
typedef struct {
    void* data;
    size_t len;
    size_t cap;
} slop_set_elements_result;

static inline slop_set_elements_result slop_set_elements_raw(slop_arena* arena, slop_map* set) {
    size_t count = set->len;
    if (count == 0) {
        return (slop_set_elements_result){NULL, 0, 0};
    }
    void* result = slop_arena_alloc(arena, count * set->key_size);
    size_t idx = 0;
    for (size_t i = 0; i < set->cap && idx < count; i++) {
        if (set->entries[i].occupied) {
            memcpy((char*)result + idx * set->key_size, set->entries[i].key, set->key_size);
            idx++;
        }
    }
    return (slop_set_elements_result){result, count, count};
}

/* ============================================================
 * Typed String-Keyed Map (generic via macro)
 *
 * Generates a type-safe wrapper around slop_map for (Map String V) types.
 * The typedef allows assignment compatibility while providing typed accessors.
 * ============================================================ */

#define SLOP_STRING_MAP_DEFINE(V, Name, OptName) \
    typedef slop_map Name; \
    \
    static inline Name Name##_new(slop_arena* arena, size_t cap) { \
        return slop_map_new(arena, cap, sizeof(slop_string), slop_hash_string, slop_eq_string); \
    } \
    \
    static inline OptName Name##_get(Name* map, slop_string key) { \
        void* v = slop_map_get(map, &key); \
        if (v) return (OptName){ .has_value = true, .value = *(V*)v }; \
        return (OptName){ .has_value = false }; \
    } \
    \
    static inline void Name##_put(slop_arena* arena, Name* map, slop_string key, V value) { \
        V* stored = (V*)slop_arena_alloc(arena, sizeof(V)); \
        *stored = value; \
        slop_map_put(arena, map, &key, stored); \
    } \
    \
    static inline bool Name##_has(Name* map, slop_string key) { \
        return slop_map_get(map, &key) != NULL; \
    }

/* ============================================================
 * Generic Map Operations (for transpiled SLOP code)
 *
 * Integer-keyed map with fixed-size value storage (up to 256 bytes).
 * Used for SLOP Map types with integer keys (e.g., Map PetId Pet).
 * ============================================================ */

/* Entry states for hash map with tombstone support */
typedef enum {
    SLOP_GMAP_EMPTY = 0,      /* Never used - stops probing */
    SLOP_GMAP_OCCUPIED = 1,   /* Contains valid key/value */
    SLOP_GMAP_TOMBSTONE = 2   /* Deleted - continue probing */
} slop_gmap_state;

/* Generic map entry with fixed-size value storage */
typedef struct slop_gmap_entry_t {
    int64_t gmap_key;
    uint64_t gmap_hash;           /* cached hash value for O(1) lookup */
    uint8_t gmap_value[256];
    size_t gmap_value_size;
    slop_gmap_state gmap_state;   /* EMPTY, OCCUPIED, or TOMBSTONE */
} slop_gmap_entry_t;

typedef struct slop_gmap_t {
    size_t gmap_len;
    size_t gmap_cap;
    slop_gmap_entry_t* gmap_entries;
} slop_gmap_t;

/* Hash function for integer keys - splitmix64 (same as slop_hash_int) */
static inline uint64_t _slop_gmap_hash(int64_t key) {
    uint64_t x = (uint64_t)key;
    x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
    x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
    return x ^ (x >> 31);
}

/* Grow and rehash the map when load factor exceeds 75% */
static inline void _slop_gmap_grow(slop_gmap_t* m) {
    size_t old_cap = m->gmap_cap;
    slop_gmap_entry_t* old_entries = m->gmap_entries;

    size_t new_cap = old_cap * 2;
    m->gmap_entries = (slop_gmap_entry_t*)calloc(new_cap, sizeof(slop_gmap_entry_t));
    m->gmap_cap = new_cap;
    m->gmap_len = 0;

    /* Rehash only occupied entries (tombstones are discarded) */
    for (size_t i = 0; i < old_cap; i++) {
        if (old_entries[i].gmap_state == SLOP_GMAP_OCCUPIED) {
            uint64_t hash = old_entries[i].gmap_hash;
            size_t idx = hash % new_cap;

            for (size_t j = 0; j < new_cap; j++) {
                size_t probe = (idx + j) % new_cap;
                if (m->gmap_entries[probe].gmap_state == SLOP_GMAP_EMPTY) {
                    m->gmap_entries[probe] = old_entries[i];
                    m->gmap_len++;
                    break;
                }
            }
        }
    }
    free(old_entries);
}

/* Create empty generic map */
static inline void* map_empty(void) {
    slop_gmap_t* m = (slop_gmap_t*)malloc(sizeof(slop_gmap_t));
    m->gmap_len = 0;
    m->gmap_cap = SLOP_MAP_INITIAL_CAPACITY;
    m->gmap_entries = (slop_gmap_entry_t*)calloc(m->gmap_cap, sizeof(slop_gmap_entry_t));
    return m;
}

/* Check if key exists - O(1) average using hash + linear probing */
static inline bool map_has(void* gmap_ptr, int64_t gmap_lookup_key) {
    slop_gmap_t* m = (slop_gmap_t*)gmap_ptr;
    if (m->gmap_cap == 0) return false;

    uint64_t hash = _slop_gmap_hash(gmap_lookup_key);
    size_t idx = hash % m->gmap_cap;

    for (size_t i = 0; i < m->gmap_cap; i++) {
        size_t probe = (idx + i) % m->gmap_cap;
        slop_gmap_state state = m->gmap_entries[probe].gmap_state;
        if (state == SLOP_GMAP_EMPTY) {
            return false;  /* Empty slot = not found */
        }
        if (state == SLOP_GMAP_OCCUPIED &&
            m->gmap_entries[probe].gmap_hash == hash &&
            m->gmap_entries[probe].gmap_key == gmap_lookup_key) {
            return true;
        }
        /* TOMBSTONE: continue probing */
    }
    return false;
}

/* Remove key - O(1) average using hash + linear probing with tombstone */
static inline void* map_remove(void* gmap_ptr, int64_t gmap_lookup_key) {
    slop_gmap_t* m = (slop_gmap_t*)gmap_ptr;
    if (m->gmap_cap == 0) return m;

    uint64_t hash = _slop_gmap_hash(gmap_lookup_key);
    size_t idx = hash % m->gmap_cap;

    for (size_t i = 0; i < m->gmap_cap; i++) {
        size_t probe = (idx + i) % m->gmap_cap;
        slop_gmap_state state = m->gmap_entries[probe].gmap_state;
        if (state == SLOP_GMAP_EMPTY) {
            return m;  /* Not found */
        }
        if (state == SLOP_GMAP_OCCUPIED &&
            m->gmap_entries[probe].gmap_hash == hash &&
            m->gmap_entries[probe].gmap_key == gmap_lookup_key) {
            m->gmap_entries[probe].gmap_state = SLOP_GMAP_TOMBSTONE;
            m->gmap_len--;
            return m;
        }
        /* TOMBSTONE: continue probing */
    }
    return m;
}

/* Map put - implemented as a macro to handle any value type */
/* Returns the map pointer (functional style but mutates in place) */
#define map_put(gmap_ptr, gmap_k, gmap_v) \
    _slop_map_put_impl((gmap_ptr), (gmap_k), &(gmap_v), sizeof(gmap_v))

static inline void* _slop_map_put_impl(void* gmap_ptr, int64_t gmap_k, const void* gmap_v, size_t gmap_vsz) {
    slop_gmap_t* m = (slop_gmap_t*)gmap_ptr;

    /* Grow if load factor > 75% */
    if (m->gmap_len * 4 >= m->gmap_cap * 3) {
        _slop_gmap_grow(m);
    }

    uint64_t hash = _slop_gmap_hash(gmap_k);
    size_t idx = hash % m->gmap_cap;
    size_t first_tombstone = (size_t)-1;  /* Track first tombstone for reuse */

    for (size_t i = 0; i < m->gmap_cap; i++) {
        size_t probe = (idx + i) % m->gmap_cap;
        slop_gmap_state state = m->gmap_entries[probe].gmap_state;

        if (state == SLOP_GMAP_EMPTY) {
            /* Use first tombstone if found, otherwise use this empty slot */
            size_t insert_at = (first_tombstone != (size_t)-1) ? first_tombstone : probe;
            m->gmap_entries[insert_at].gmap_key = gmap_k;
            m->gmap_entries[insert_at].gmap_hash = hash;
            memcpy(m->gmap_entries[insert_at].gmap_value, gmap_v, gmap_vsz);
            m->gmap_entries[insert_at].gmap_value_size = gmap_vsz;
            m->gmap_entries[insert_at].gmap_state = SLOP_GMAP_OCCUPIED;
            m->gmap_len++;
            return m;
        }

        if (state == SLOP_GMAP_TOMBSTONE) {
            /* Remember first tombstone for potential reuse */
            if (first_tombstone == (size_t)-1) {
                first_tombstone = probe;
            }
            continue;  /* Keep probing to check for existing key */
        }

        /* state == SLOP_GMAP_OCCUPIED */
        if (m->gmap_entries[probe].gmap_hash == hash &&
            m->gmap_entries[probe].gmap_key == gmap_k) {
            /* Update existing entry */
            memcpy(m->gmap_entries[probe].gmap_value, gmap_v, gmap_vsz);
            m->gmap_entries[probe].gmap_value_size = gmap_vsz;
            return m;
        }
    }

    /* Table is full (shouldn't happen with proper load factor) */
    /* Use first tombstone if available */
    if (first_tombstone != (size_t)-1) {
        m->gmap_entries[first_tombstone].gmap_key = gmap_k;
        m->gmap_entries[first_tombstone].gmap_hash = hash;
        memcpy(m->gmap_entries[first_tombstone].gmap_value, gmap_v, gmap_vsz);
        m->gmap_entries[first_tombstone].gmap_value_size = gmap_vsz;
        m->gmap_entries[first_tombstone].gmap_state = SLOP_GMAP_OCCUPIED;
        m->gmap_len++;
    }
    return m;
}

/* Map get - returns Option-like struct matching generated types layout:
 * typedef struct { uint8_t tag; union { T some; } data; } slop_option_T;
 * We use a large enough buffer to hold any value type.
 */
typedef struct {
    uint8_t tag;
    union { uint8_t some[256]; } data;
} slop_gmap_option_raw;

static inline slop_gmap_option_raw _slop_map_get_raw(void* gmap_ptr, int64_t gmap_k) {
    slop_gmap_option_raw result = {1, {{0}}}; /* tag=1 means none */
    slop_gmap_t* m = (slop_gmap_t*)gmap_ptr;
    if (m->gmap_cap == 0) return result;

    uint64_t hash = _slop_gmap_hash(gmap_k);
    size_t idx = hash % m->gmap_cap;

    for (size_t i = 0; i < m->gmap_cap; i++) {
        size_t probe = (idx + i) % m->gmap_cap;
        slop_gmap_state state = m->gmap_entries[probe].gmap_state;

        if (state == SLOP_GMAP_EMPTY) {
            return result;  /* Not found */
        }

        if (state == SLOP_GMAP_OCCUPIED &&
            m->gmap_entries[probe].gmap_hash == hash &&
            m->gmap_entries[probe].gmap_key == gmap_k) {
            result.tag = 0; /* tag=0 means some */
            memcpy(result.data.some, m->gmap_entries[probe].gmap_value,
                   m->gmap_entries[probe].gmap_value_size);
            return result;
        }
        /* TOMBSTONE: continue probing */
    }
    return result;
}

/* Map values - returns List-like struct matching generated types layout:
 * typedef struct { T* data; size_t len; size_t cap; } slop_list_T;
 */
typedef struct { uint8_t* data; size_t len; size_t cap; } slop_gmap_list;

static inline slop_gmap_list _slop_map_values_raw(void* gmap_ptr, size_t value_size) {
    slop_gmap_t* m = (slop_gmap_t*)gmap_ptr;
    if (m->gmap_len == 0) {
        return (slop_gmap_list){NULL, 0, 0};
    }
    uint8_t* data = (uint8_t*)malloc(m->gmap_len * value_size);
    size_t write_idx = 0;
    /* Scan all slots since entries are scattered via hash */
    for (size_t idx = 0; idx < m->gmap_cap; idx++) {
        if (m->gmap_entries[idx].gmap_state == SLOP_GMAP_OCCUPIED) {
            memcpy(data + (write_idx * value_size),
                   m->gmap_entries[idx].gmap_value, value_size);
            write_idx++;
        }
    }
    return (slop_gmap_list){data, m->gmap_len, m->gmap_len};
}

/* Map keys - returns list of all int64_t keys */
static inline slop_list_int _slop_map_keys_raw(void* gmap_ptr) {
    slop_gmap_t* m = (slop_gmap_t*)gmap_ptr;
    if (m->gmap_len == 0) {
        return (slop_list_int){0, 0, NULL};
    }
    int64_t* data = (int64_t*)malloc(m->gmap_len * sizeof(int64_t));
    size_t write_idx = 0;
    /* Scan all slots since entries are scattered via hash */
    for (size_t idx = 0; idx < m->gmap_cap; idx++) {
        if (m->gmap_entries[idx].gmap_state == SLOP_GMAP_OCCUPIED) {
            data[write_idx++] = m->gmap_entries[idx].gmap_key;
        }
    }
    return (slop_list_int){m->gmap_len, m->gmap_len, data};
}

static inline slop_list_int map_keys(void* m) {
    return _slop_map_keys_raw(m);
}

/* Take first n elements from list - modifies in place and returns */
static inline slop_gmap_list _slop_take_raw(slop_gmap_list lst, int64_t n) {
    if ((size_t)n < lst.len) lst.len = (size_t)n;
    return lst;
}

/*
 * Type-specific map_get/map_values/take must be generated by the transpiler.
 * Define a macro that generates them for a given value type:
 */
#define SLOP_MAP_OPS_DEFINE(V, OptName, ListName) \
    static inline OptName map_get_##V(void* m, int64_t k) { \
        slop_gmap_option_raw raw = _slop_map_get_raw(m, k); \
        OptName result; \
        result.tag = raw.tag; \
        if (raw.tag == 0) memcpy(&result.data.some, raw.some, sizeof(V)); \
        return result; \
    } \
    static inline ListName map_values_##V(void* m) { \
        slop_gmap_list raw = _slop_map_values_raw(m, sizeof(V)); \
        return (ListName){(V*)raw.data, raw.len, raw.cap}; \
    } \
    static inline ListName take_##V(int64_t n, ListName lst) { \
        if ((size_t)n < lst.len) lst.len = (size_t)n; \
        return lst; \
    }

/*
 * Type-specific map operations are generated by the transpiler.
 * Use SLOP_MAP_GET_DEFINE(ValueType, OptionType) to define map_get for a type.
 * Use SLOP_MAP_VALUES_DEFINE(ValueType, ListType) to define map_values for a type.
 */

#define SLOP_MAP_GET_DEFINE(V, OptType) \
    static inline OptType map_get_##V(void* m, int64_t k) { \
        slop_gmap_option_raw raw = _slop_map_get_raw(m, k); \
        OptType result = {0}; \
        result.has_value = (raw.tag == 0); \
        if (raw.tag == 0) memcpy(&result.value, raw.data.some, sizeof(V)); \
        return result; \
    }

#define SLOP_MAP_VALUES_DEFINE(V, ListType) \
    static inline ListType map_values_##V(void* m) { \
        slop_gmap_list raw = _slop_map_values_raw(m, sizeof(V)); \
        return (ListType){(V*)raw.data, raw.len, raw.cap}; \
    }

#define SLOP_TAKE_DEFINE(V, ListType) \
    static inline ListType take_##V(int64_t n, ListType lst) { \
        if ((size_t)n < lst.len) lst.len = (size_t)n; \
        return lst; \
    }

/* Generic take that works with any list type */
#define take(n, lst) ({ \
    __auto_type _take_lst = (lst); \
    int64_t _take_n = (n); \
    if ((size_t)_take_n < _take_lst.len) _take_lst.len = (size_t)_take_n; \
    _take_lst; \
})

/* ============================================================
 * Option Type (generic via macros)
 * ============================================================ */

#define SLOP_OPTION_DEFINE(T, Name) \
    typedef struct { \
        bool has_value; \
        T value; \
    } Name; \
    \
    static inline Name Name##_some(T val) { \
        return (Name){true, val}; \
    } \
    \
    static inline Name Name##_none(void) { \
        return (Name){false}; \
    } \
    \
    static inline bool Name##_is_some(Name opt) { \
        return opt.has_value; \
    } \
    \
    static inline T Name##_unwrap(Name opt) { \
        SLOP_PRE(opt.has_value, "unwrap on Some"); \
        return opt.value; \
    }

SLOP_OPTION_DEFINE(int64_t, slop_option_int)
SLOP_OPTION_DEFINE(double, slop_option_float)
SLOP_OPTION_DEFINE(slop_string, slop_option_string)
SLOP_OPTION_DEFINE(void*, slop_option_ptr)
SLOP_OPTION_DEFINE(bool, slop_option_bool)

/* Pre-define common string-keyed maps (must come after SLOP_OPTION_DEFINE) */
SLOP_STRING_MAP_DEFINE(slop_string, slop_map_string_string, slop_option_string)
SLOP_STRING_MAP_DEFINE(int64_t, slop_map_string_int, slop_option_int)

/* ============================================================
 * Result Type (generic via macros)
 * ============================================================ */

#define SLOP_RESULT_DEFINE(T, E, Name) \
    typedef struct { \
        bool is_ok; \
        union { T ok; E err; } data; \
    } Name; \
    \
    static inline Name Name##_ok(T val) { \
        Name r; r.is_ok = true; r.data.ok = val; return r; \
    } \
    \
    static inline Name Name##_err(E e) { \
        Name r; r.is_ok = false; r.data.err = e; return r; \
    } \
    \
    static inline T Name##_unwrap(Name res) { \
        SLOP_PRE(res.is_ok, "unwrap on Ok"); \
        return res.data.ok; \
    }

/* Common error type */
typedef enum {
    SLOP_ERR_NONE = 0,
    SLOP_ERR_NULL_POINTER,
    SLOP_ERR_OUT_OF_BOUNDS,
    SLOP_ERR_INVALID_ARGUMENT,
    SLOP_ERR_NOT_FOUND,
    SLOP_ERR_ALREADY_EXISTS,
    SLOP_ERR_IO_ERROR,
    SLOP_ERR_PARSE_ERROR,
    SLOP_ERR_INSUFFICIENT_FUNDS,  /* Example domain error */
} slop_error;

static inline const char* slop_error_str(slop_error e) {
    switch (e) {
        case SLOP_ERR_NONE: return "none";
        case SLOP_ERR_NULL_POINTER: return "null pointer";
        case SLOP_ERR_OUT_OF_BOUNDS: return "out of bounds";
        case SLOP_ERR_INVALID_ARGUMENT: return "invalid argument";
        case SLOP_ERR_NOT_FOUND: return "not found";
        case SLOP_ERR_ALREADY_EXISTS: return "already exists";
        case SLOP_ERR_IO_ERROR: return "io error";
        case SLOP_ERR_PARSE_ERROR: return "parse error";
        case SLOP_ERR_INSUFFICIENT_FUNDS: return "insufficient funds";
        default: return "unknown error";
    }
}

SLOP_RESULT_DEFINE(int64_t, slop_error, slop_result_int)
SLOP_RESULT_DEFINE(void*, slop_error, slop_result_ptr)
SLOP_RESULT_DEFINE(slop_string, slop_error, slop_result_string)

/* ============================================================
 * Time
 * ============================================================ */

#ifdef _WIN32
#include <windows.h>
static inline int64_t slop_now_ms(void) {
    return (int64_t)GetTickCount64();
}
#else
#include <time.h>
static inline int64_t slop_now_ms(void) {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
#endif

static inline void slop_sleep_ms(int64_t ms) {
#ifdef _WIN32
    Sleep((DWORD)ms);
#else
    struct timespec ts = {ms / 1000, (ms % 1000) * 1000000};
    nanosleep(&ts, NULL);
#endif
}

static inline void sleep_ms(int64_t ms) {
    slop_sleep_ms(ms);
}

/* ============================================================
 * Integer formatting (for output)
 * ============================================================ */

static inline slop_string slop_int_to_string(slop_arena* arena, int64_t n) {
    char buf[32];
    int len = snprintf(buf, sizeof(buf), "%ld", (long)n);
    return slop_string_new_len(arena, buf, len);
}

static inline slop_string slop_float_to_string(slop_arena* arena, double n) {
    char buf[64];
    int len = snprintf(buf, sizeof(buf), "%g", n);
    return slop_string_new_len(arena, buf, len);
}

/* ============================================================
 * Stderr output helpers
 * ============================================================ */

static inline void slop_eprint(const char* s) {
    fputs(s, stderr);
}

static inline void slop_eputc(int c) {
    fputc(c, stderr);
}

#endif /* SLOP_RUNTIME_H */
