/*
 * tests/suite/infra.c — El runtime infrastructure functions for the test suite.
 *
 * The El test suite compiles runtime/*.el files into tests.c via elc, then links
 * only against el_seed.c (not the full el_runtime.c, which would cause ~120
 * duplicate symbol errors). This file provides the infrastructure symbols that
 * tests.c references but that are NOT defined in either the generated El code or
 * el_seed.c:
 *
 *   ─ ElList machinery: el_list_empty, el_list_append, el_list_len, el_list_get,
 *     el_str_concat, native_list_*, len, get
 *   ─ el_runtime_init_args (needed by the generated main())
 *   ─ Stubs for http_*, engram_*, el_html_sanitize (not needed by tests)
 *
 * These implementations are derived from el_runtime.c.
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include "el_seed.h"
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* ── String arena (minimal — no HTTP request lifecycle needed) ─────────────── */

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

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

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

/* ── el_str_concat ────────────────────────────────────────────────────────── */

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);
    size_t 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);
}

/* ── ElList ───────────────────────────────────────────────────────────────── */

#define EL_MAGIC_LIST 0xE15710A1u

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

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

static ElList* list_alloc(int64_t cap) {
    if (cap < 4) cap = 4;
    ElList* lst = malloc(sizeof(ElList));
    if (!lst) { fputs("infra: out of memory\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("infra: out of memory\n", stderr); exit(1); }
    return lst;
}

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

el_val_t el_list_len(el_val_t listv) {
    ElList* lst = (ElList*)(uintptr_t)listv;
    if (!lst) return 0;
    return lst->length;
}

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

el_val_t el_list_append(el_val_t listv, el_val_t elem) {
    ElList* old = (ElList*)(uintptr_t)listv;
    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 new_cap = old->capacity > 0 ? old->capacity * 2 : 4;
            el_val_t* grown = realloc(old->elems, (size_t)new_cap * sizeof(el_val_t));
            if (!grown) { fputs("infra: out of memory\n", stderr); exit(1); }
            old->elems    = grown;
            old->capacity = new_cap;
        }
        old->elems[old->length++] = elem;
        return listv;
    }

    int64_t new_cap = old->length + 1;
    if (new_cap < 4) new_cap = 4;
    ElList* fresh = malloc(sizeof(ElList));
    if (!fresh) { fputs("infra: out of memory\n", stderr); exit(1); }
    fresh->hdr.magic    = EL_MAGIC_LIST;
    fresh->hdr.refcount = 1;
    fresh->length       = old->length + 1;
    fresh->capacity     = new_cap;
    fresh->elems        = malloc((size_t)new_cap * sizeof(el_val_t));
    if (!fresh->elems) { fputs("infra: out of memory\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);
}

/* ── native_list aliases ──────────────────────────────────────────────────── */

el_val_t native_list_empty(void)                          { return el_list_empty(); }
el_val_t native_list_append(el_val_t list, el_val_t elem) { return el_list_append(list, elem); }
el_val_t native_list_get(el_val_t list, el_val_t index)   { return el_list_get(list, index); }
el_val_t native_list_len(el_val_t list)                   { return el_list_len(list); }

/* ── len / get aliases ─────────────────────────────────────────────────────── */

el_val_t len(el_val_t list) { return el_list_len(list); }
el_val_t get(el_val_t list, el_val_t index) { return el_list_get(list, index); }

/* ── 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]));
    }
}

/* ── HTTP stubs (not exercised by test suite) ─────────────────────────────── */

el_val_t http_post(el_val_t url, el_val_t body) {
    (void)url; (void)body;
    return EL_STR("");
}

el_val_t http_post_json(el_val_t url, el_val_t body) {
    (void)url; (void)body;
    return EL_STR("");
}

el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers) {
    (void)url; (void)body; (void)headers;
    return EL_STR("");
}

el_val_t http_response(el_val_t status, el_val_t headers, el_val_t body) {
    (void)status; (void)headers; (void)body;
    return EL_STR("");
}

void http_serve(el_val_t port, el_val_t handler) {
    (void)port; (void)handler;
}

void http_serve_v2(el_val_t port, el_val_t handler) {
    (void)port; (void)handler;
}

/* ── el_html_sanitize stub ────────────────────────────────────────────────── */

el_val_t el_html_sanitize(el_val_t input, el_val_t allowlist) {
    (void)allowlist;
    return input;
}

/* ── Engram stubs (not exercised by test suite) ───────────────────────────── */

el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience) {
    (void)content; (void)node_type; (void)salience;
    return EL_STR("");
}

el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
                          el_val_t salience, el_val_t importance, el_val_t confidence,
                          el_val_t tier, el_val_t tags) {
    (void)content; (void)node_type; (void)label; (void)salience;
    (void)importance; (void)confidence; (void)tier; (void)tags;
    return EL_STR("");
}

el_val_t engram_node_layered(el_val_t content, el_val_t node_type, el_val_t label,
                              el_val_t salience, el_val_t certainty, el_val_t confidence,
                              el_val_t tier, el_val_t layer_id, el_val_t tags) {
    (void)content; (void)node_type; (void)label; (void)salience;
    (void)certainty; (void)confidence; (void)tier; (void)layer_id; (void)tags;
    return EL_STR("");
}

el_val_t engram_add_layer(el_val_t name, el_val_t priority, el_val_t suppressible,
                           el_val_t transparent, el_val_t injectable) {
    (void)name; (void)priority; (void)suppressible; (void)transparent; (void)injectable;
    return EL_STR("");
}

el_val_t engram_remove_layer(el_val_t layer_id) {
    (void)layer_id;
    return (el_val_t)1;
}

el_val_t engram_list_layers(void) { return EL_STR("[]"); }
el_val_t engram_list_layers_json(void) { return EL_STR("[]"); }

el_val_t engram_get_node(el_val_t id) { (void)id; return EL_STR(""); }
el_val_t engram_get_node_json(el_val_t id) { (void)id; return EL_STR("{}"); }

void engram_strengthen(el_val_t node_id) { (void)node_id; }
void engram_forget(el_val_t node_id)     { (void)node_id; }

el_val_t engram_node_count(void)  { return 0; }
el_val_t engram_edge_count(void)  { return 0; }
el_val_t engram_stats_json(void)  { return EL_STR("{}"); }

void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation) {
    (void)from_id; (void)to_id; (void)weight; (void)relation;
}

el_val_t engram_edge_between(el_val_t from_id, el_val_t to_id) {
    (void)from_id; (void)to_id;
    return (el_val_t)0;
}

el_val_t engram_search(el_val_t query, el_val_t limit) {
    (void)query; (void)limit;
    return el_list_empty();
}

el_val_t engram_search_json(el_val_t query, el_val_t limit) {
    (void)query; (void)limit;
    return EL_STR("[]");
}

el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset) {
    (void)limit; (void)offset;
    return el_list_empty();
}

el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset) {
    (void)limit; (void)offset;
    return EL_STR("[]");
}

el_val_t engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset) {
    (void)node_type; (void)limit; (void)offset;
    return EL_STR("[]");
}

el_val_t engram_neighbors(el_val_t node_id, el_val_t limit) {
    (void)node_id; (void)limit;
    return el_list_empty();
}

el_val_t engram_neighbors_filtered(el_val_t node_id, el_val_t relation, el_val_t limit) {
    (void)node_id; (void)relation; (void)limit;
    return el_list_empty();
}

el_val_t engram_neighbors_json(el_val_t node_id, el_val_t limit) {
    (void)node_id; (void)limit;
    return EL_STR("[]");
}

el_val_t engram_load(el_val_t path) { (void)path; return (el_val_t)1; }
el_val_t engram_save(el_val_t path) { (void)path; return (el_val_t)1; }

el_val_t engram_activate(el_val_t node_ids, el_val_t spread, el_val_t decay) {
    (void)node_ids; (void)spread; (void)decay;
    return el_list_empty();
}

el_val_t engram_activate_json(el_val_t node_ids_json, el_val_t spread, el_val_t decay) {
    (void)node_ids_json; (void)spread; (void)decay;
    return EL_STR("[]");
}

el_val_t engram_compile_layered_json(el_val_t node_id, el_val_t target_tokens,
                                      el_val_t strategy) {
    (void)node_id; (void)target_tokens; (void)strategy;
    return EL_STR("{}");
}
