/*
 * el_runtime.h — El language C runtime header
 *
 * Declares all built-in functions available to compiled El programs.
 * Include this in every generated .c file.
 *
 * Value model:
 *   All El values are represented as el_val_t (= int64_t).
 *   On 64-bit systems a pointer fits in int64_t.
 *   String values are cast: (el_val_t)(uintptr_t)"hello"
 *   Integer values are stored directly.
 *   This lets arithmetic work naturally while still passing strings around.
 *
 * Type conventions (El -> C):
 *   String  -> el_val_t  (holds const char* via uintptr_t cast)
 *   Int     -> el_val_t
 *   Bool    -> el_val_t  (0 = false, nonzero = true)
 *   Any     -> el_val_t
 *   Void    -> void
 *
 * Macros for convenience:
 *   EL_STR(s)    cast string literal to el_val_t
 *   EL_CSTR(v)   cast el_val_t back to const char*
 *   EL_INT(v)    identity — el_val_t is already int64_t
 *   EL_NULL      null / zero value
 *   EL_FALSE     boolean false (0)
 *   EL_TRUE      boolean true (1)
 *
 * Link requirements:
 *   -lcurl     — required for the HTTP client (http_get, http_post, llm_*).
 *   -lpthread  — required for the HTTP server (one detached thread per
 *                connection, capped at 64 concurrent).
 *   -loqs      — optional; required only when liboqs is installed and the
 *                pq_* / sha3_256_hex entry points are needed. Detected at
 *                compile time via __has_include(<oqs/oqs.h>).
 *   -lcrypto   — optional; pulled in alongside -loqs. Used for X25519 in
 *                pq_hybrid_* and HKDF-SHA256 derivation.
 *
 * Canonical compile command:
 *   cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
 *      -o <out> <prog>.c el-compiler/runtime/el_runtime.c
 *
 * With liboqs (post-quantum stack):
 *   cc -std=c11 -I el-compiler/runtime -lcurl -lpthread -loqs -lcrypto \
 *      -o <out> <prog>.c el-compiler/runtime/el_runtime.c
 */

#pragma once

#include <stdint.h>
#include <stdlib.h>

typedef int64_t el_val_t;

#define EL_STR(s)  ((el_val_t)(uintptr_t)(s))
#define EL_CSTR(v) ((const char*)(uintptr_t)(v))
#define EL_INT(v)  (v)
#define EL_NULL    ((el_val_t)0)
#define EL_FALSE   ((el_val_t)0)
#define EL_TRUE    ((el_val_t)1)

/* Float values share the el_val_t (int64) slot via a bit-cast.
 * The codegen emits Float literals as `el_from_float(<dbl>)` so the
 * underlying bits represent the IEEE 754 double. Float-aware builtins
 * (math, format, json) round-trip via these helpers. */
static inline double el_to_float(el_val_t v) {
    union { int64_t i; double f; } u;
    u.i = (int64_t)v;
    return u.f;
}

static inline el_val_t el_from_float(double f) {
    union { double f; int64_t i; } u;
    u.f = f;
    return (el_val_t)u.i;
}

#ifdef __cplusplus
extern "C" {
#endif

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

el_val_t  println(el_val_t s);
el_val_t  print(el_val_t s);
el_val_t  readline(void);

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

el_val_t  el_str_concat(el_val_t a, el_val_t b);
el_val_t  str_eq(el_val_t a, el_val_t b);
el_val_t  str_starts_with(el_val_t s, el_val_t prefix);
el_val_t  str_ends_with(el_val_t s, el_val_t suffix);
el_val_t  str_len(el_val_t s);
el_val_t  str_concat(el_val_t a, el_val_t b);
el_val_t  int_to_str(el_val_t n);
el_val_t  str_to_int(el_val_t s);
el_val_t  native_str_to_int(el_val_t s);
el_val_t  str_slice(el_val_t s, el_val_t start, el_val_t end);
el_val_t  str_contains(el_val_t s, el_val_t sub);
el_val_t  str_replace(el_val_t s, el_val_t from, el_val_t to);
el_val_t  str_to_upper(el_val_t s);
el_val_t  str_to_lower(el_val_t s);
el_val_t  str_trim(el_val_t s);

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

el_val_t  el_abs(el_val_t n);
el_val_t  el_max(el_val_t a, el_val_t b);
el_val_t  el_min(el_val_t a, el_val_t b);

/* ── Refcount (ARC) ──────────────────────────────────────────────────────────
 * Lists and Maps carry a refcount. Strings and ints do not — el_retain and
 * el_release are safe no-ops on non-refcounted values (they sniff a magic
 * header at offset 0 and only act if the magic matches).
 *
 * Codegen emits these at let-binding shadowing, function entry (params), and
 * function exit (locals other than the returned value). The refcount lets
 * el_list_append and el_map_set mutate in place when uniquely owned (cheap)
 * and copy-on-write when shared (preserves persistent semantics across
 * accumulator patterns in the compiler itself).                              */

void      el_retain(el_val_t v);
void      el_release(el_val_t v);

/* ── Scoped arena (CLI use) ───────────────────────────────────────────────── */
el_val_t  el_arena_push(void);
el_val_t  el_arena_pop(el_val_t mark);

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

el_val_t  el_list_new(el_val_t count, ...);
el_val_t  el_list_len(el_val_t list);
el_val_t  el_list_get(el_val_t list, el_val_t index);
el_val_t  el_list_append(el_val_t list, el_val_t elem);
el_val_t  el_list_empty(void);
el_val_t  el_list_clone(el_val_t list);

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

el_val_t  el_map_new(el_val_t pair_count, ...);
el_val_t  el_get_field(el_val_t map, el_val_t key);
el_val_t  el_map_get(el_val_t map, el_val_t key);
el_val_t  el_map_set(el_val_t map, el_val_t key, el_val_t value);

/* ── HTTP ─────────────────────────────────────────────────────────────────── */

el_val_t  http_get(el_val_t url);
el_val_t  http_post(el_val_t url, el_val_t body);
el_val_t  http_post_json(el_val_t url, el_val_t json_body);
el_val_t  http_get_with_headers(el_val_t url, el_val_t headers_map);
el_val_t  http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_map);
el_val_t  http_post_json_with_headers(el_val_t url, el_val_t headers_map, el_val_t json_body);
el_val_t  http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header);
el_val_t  http_delete(el_val_t url);
el_val_t  http_serve(el_val_t port, el_val_t handler);
el_val_t  http_set_handler(el_val_t name);

/* HTTP server v2 ─────────────────────────────────────────────────────────────
 * Same dispatch model as http_serve, but the handler signature is widened:
 *
 *     el_val_t handler(method, path, headers_map, body)
 *
 * `headers_map` is an ElMap from lowercased header name → header value (both
 * Strings). Repeated headers are joined with ", " per RFC 7230.
 *
 * Response value: the handler may return either
 *   (a) a plain body string — same auto-content-type / 200-OK behaviour as
 *       http_serve (3-arg) — or
 *   (b) a response envelope built with `http_response(status, headers_json,
 *       body)`. The runtime detects the envelope discriminator
 *       `"el_http_response":1` at the start of the returned string and
 *       unpacks status / headers / body before sending.
 *
 * The 3-arg http_serve(port, handler) remains supported unchanged for
 * existing handlers (e.g. products/web/server.el): it dispatches with
 * (method, path, body), hardcodes 200 OK, and auto-detects content type. */
el_val_t  http_serve_v2(el_val_t port, el_val_t handler);
el_val_t  http_set_handler_v2(el_val_t name);

/* Build an HTTP response envelope. `headers_json` should be a JSON object
 * literal like `{"WWW-Authenticate":"Basic"}` (or "" / "{}" for none). The
 * returned string carries the discriminator `{"el_http_response":1,...}`
 * which the runtime's send-path detects and unpacks. Detection happens
 * uniformly inside http_send_response, so a 3-arg handler may also return
 * an envelope. The 3-arg variant remains documented as a fixed 200-OK
 * auto-content-type contract for legacy handlers that return plain bodies. */
el_val_t  http_response(el_val_t status, el_val_t headers_json, el_val_t body);

/* SSE connection fd — set by http_worker_v2 before calling the El handler,
 * cleared afterwards. Defined in el_seed.c; called from el_runtime.c.
 * The getter is exposed as __http_conn_fd() to El programs. */
void      el_seed_set_http_conn_fd(int fd);

/* HTTP timeout — every libcurl request honors EL_HTTP_TIMEOUT_MS (default
 * 60000ms). Read lazily on first use, so setting the env var any time before
 * the first http_* call is sufficient.                                    */

/* Streaming variants — write the response body straight to a file via
 * libcurl's CURLOPT_WRITEFUNCTION = fwrite. These bypass the el_val_t string
 * wrapper entirely, so binary payloads (audio/mpeg, image/png, etc.) survive
 * embedded NUL bytes that would truncate a strlen()-based code path.
 *
 * Both honor EL_HTTP_TIMEOUT_MS, follow redirects, and accept the same
 * `headers_map` shape as http_post_with_headers (ElMap of String→String).
 *
 * Return value: 1 on success (file fully written), 0 on any failure
 * (network, file open, partial write). On failure the output file is removed
 * so callers cannot mistake a partially-written file for a valid one. */
el_val_t  http_post_to_file(el_val_t url, el_val_t body, el_val_t headers_map, el_val_t output_path);
el_val_t  http_get_to_file(el_val_t url, el_val_t headers_map, el_val_t output_path);

/* ── URL encoding ────────────────────────────────────────────────────────── */

el_val_t  url_encode(el_val_t s);   /* RFC 3986 unreserved set */
el_val_t  url_decode(el_val_t s);   /* '+' → space, %XX → byte  */

/* ── HTML allowlist sanitizer ────────────────────────────────────────────────
 * el_html_sanitize(input_html, allowlist_json) — strict allowlist HTML
 * cleaner. State-machine parser; tag/attribute names compared case-
 * insensitively against the allowlist; `<a href>` / `<… src>` URL schemes
 * validated (http, https, mailto, fragment-only, or relative); whole-
 * subtree drop for script / style / iframe / object / embed / form; HTML-
 * escapes free text outside dropped subtrees.
 *
 * The allowlist is JSON of the form
 *     {"p":[],"a":["href","title"],"strong":[],...}
 * where each value is the array of attribute names allowed for that tag. */
el_val_t  el_html_sanitize(el_val_t input_html, el_val_t allowlist_json);
el_val_t  html_raw(el_val_t s);
el_val_t  html_escape(el_val_t s);

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

el_val_t  fs_read(el_val_t path);
el_val_t  fs_write(el_val_t path, el_val_t content);
el_val_t  fs_list(el_val_t path);
el_val_t  fs_list_json(el_val_t path);
el_val_t  fs_exists(el_val_t path);
el_val_t  fs_mkdir(el_val_t path);   /* mkdir -p, mode 0755                */

/* Length-explicit binary write. `length` is an Int (el_val_t holding the
 * byte count). The caller knows the length from context — typically because
 * `bytes` came from base64_decode (which produces a magic-tagged binary
 * buffer with embedded NULs possible) and the caller already tracks the
 * decoded length, OR because the bytes came from a fixed-size source
 * (sha256_bytes = 32, hmac_sha256_bytes = 32). Bypasses strlen entirely.
 *
 * Returns 1 on success, 0 on failure (invalid path, can't open, partial
 * write, negative length). On partial-write failure, the file is removed
 * so callers cannot read back a truncated artefact. */
el_val_t  fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t length);

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

el_val_t  json_get(el_val_t json, el_val_t key);
el_val_t  json_parse(el_val_t s);
el_val_t  json_stringify(el_val_t v);
el_val_t  json_get_string(el_val_t json_str, el_val_t key);
el_val_t  json_get_int(el_val_t json_str, el_val_t key);
el_val_t  json_get_float(el_val_t json_str, el_val_t key);
el_val_t  json_get_bool(el_val_t json_str, el_val_t key);
el_val_t  json_get_raw(el_val_t json_str, el_val_t key);
el_val_t  json_set(el_val_t json_str, el_val_t key, el_val_t value);
el_val_t  json_array_len(el_val_t json_str);
el_val_t  json_array_get(el_val_t json_str, el_val_t index);
el_val_t  json_array_get_string(el_val_t json_str, el_val_t index);
el_val_t  json_escape_string(el_val_t sv);
el_val_t  json_build_object(el_val_t kvs);
el_val_t  json_build_array(el_val_t items);

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

el_val_t  time_now(void);
el_val_t  time_now_utc(void);
el_val_t  sleep_secs(el_val_t secs);
el_val_t  sleep_ms(el_val_t ms);
el_val_t  time_format(el_val_t ts, el_val_t fmt);
el_val_t  time_to_parts(el_val_t ts);
el_val_t  time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz);
el_val_t  time_add(el_val_t ts, el_val_t n, el_val_t unit);
el_val_t  time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit);
el_val_t  now_ns(void);

/* ── Instant + Duration: first-class temporal types ──────────────────────────
 * Both types share the el_val_t (int64) slot. Instants are nanoseconds
 * since the Unix epoch; Durations are signed nanoseconds. Type discipline
 * is enforced at codegen-time: BinOps on names registered as Instant or
 * Duration route through the typed wrappers below; mismatches like
 * Instant+Instant become #error at the C compiler.
 *
 * Postfix literals — `30.seconds`, `1.hour`, `500.millis`, `30.nanos` — are
 * recognised by the parser as DurationLit AST nodes and lowered to literal
 * int64 nanoseconds at codegen time. The runtime never sees the units. */

el_val_t  el_now_instant(void);
el_val_t  now(void);
el_val_t  unix_seconds(el_val_t n);
el_val_t  unix_millis(el_val_t n);
el_val_t  instant_from_iso8601(el_val_t s);

el_val_t  el_duration_from_nanos(el_val_t ns);
el_val_t  duration_seconds(el_val_t n);
el_val_t  duration_millis(el_val_t n);
el_val_t  duration_nanos(el_val_t n);

el_val_t  el_instant_add_dur(el_val_t inst, el_val_t dur);
el_val_t  el_instant_sub_dur(el_val_t inst, el_val_t dur);
el_val_t  el_instant_diff(el_val_t a, el_val_t b);
el_val_t  el_duration_add(el_val_t a, el_val_t b);
el_val_t  el_duration_sub(el_val_t a, el_val_t b);
el_val_t  el_duration_scale(el_val_t dur, el_val_t scalar);
el_val_t  el_duration_div(el_val_t dur, el_val_t scalar);

el_val_t  el_instant_lt(el_val_t a, el_val_t b);
el_val_t  el_instant_le(el_val_t a, el_val_t b);
el_val_t  el_instant_gt(el_val_t a, el_val_t b);
el_val_t  el_instant_ge(el_val_t a, el_val_t b);
el_val_t  el_instant_eq(el_val_t a, el_val_t b);
el_val_t  el_instant_ne(el_val_t a, el_val_t b);
el_val_t  el_duration_lt(el_val_t a, el_val_t b);
el_val_t  el_duration_le(el_val_t a, el_val_t b);
el_val_t  el_duration_gt(el_val_t a, el_val_t b);
el_val_t  el_duration_ge(el_val_t a, el_val_t b);
el_val_t  el_duration_eq(el_val_t a, el_val_t b);
el_val_t  el_duration_ne(el_val_t a, el_val_t b);

el_val_t  instant_to_unix_seconds(el_val_t i);
el_val_t  instant_to_unix_millis(el_val_t i);
el_val_t  instant_to_iso8601(el_val_t i);
el_val_t  duration_to_seconds(el_val_t d);
el_val_t  duration_to_millis(el_val_t d);
el_val_t  duration_to_nanos(el_val_t d);

el_val_t  el_sleep_duration(el_val_t dur);
el_val_t  unix_timestamp(void);

el_val_t  ttl_cache_set(el_val_t key, el_val_t value);
el_val_t  ttl_cache_get(el_val_t key, el_val_t max_age);
el_val_t  ttl_cache_age(el_val_t key);

/* ── Calendar + CalendarTime + Rhythm + LocalDate/Time/DateTime ─────────────
 * Phase 1.5 of the time system. Calendar is pluggable: EarthCalendar (IANA
 * zones, Gregorian, DST) is the user-facing default; MarsCalendar,
 * CycleCalendar(period), NoCycleCalendar, RelativeCalendar handle non-Earth
 * domains.
 *
 * A Calendar interprets an Instant under a particular cycle convention and
 * produces a CalendarTime. CalendarTime carries the underlying Instant and
 * a back-pointer to its Calendar; arithmetic and formatting consult the
 * Calendar to convert ns since epoch into year/month/day/hour/minute/second
 * (or sol/phase, or cycle/phase, depending on kind).
 *
 * Storage convention: Calendar / CalendarTime / Rhythm / LocalDate /
 * LocalDateTime are heap-allocated structs whose pointers are cast into
 * el_val_t. A 24-bit magic header at offset 0 lets the runtime identify
 * the kind safely. LocalTime is small enough to live in the int64 slot
 * directly (nanos since midnight, signed).                                  */

/* Zone — opaque IANA zone or fixed offset, used by EarthCalendar.
 * `zone_id` is either an IANA name ("America/New_York", "UTC") or a fixed
 * offset string ("+05:30", "-08:00"). The runtime resolves it via tzset()
 * on first use of the owning EarthCalendar.                                  */
el_val_t  zone(el_val_t id);
el_val_t  zone_utc(void);
el_val_t  zone_local(void);
el_val_t  zone_offset(el_val_t hours, el_val_t minutes);

/* Calendar constructors. Each returns an el_val_t pointer to a heap-
 * allocated, magic-tagged Calendar struct. Calendars are interned by
 * (kind, zone_id, period_ns, epoch_ns) so identical constructors return
 * the same pointer — equality is reference equality.                        */
el_val_t  earth_calendar(el_val_t z);
el_val_t  earth_calendar_default(void);
el_val_t  mars_calendar(void);
el_val_t  cycle_calendar(el_val_t period_dur);
el_val_t  no_cycle_calendar(void);
el_val_t  relative_calendar(el_val_t epoch_inst);

/* CalendarTime constructors and methods. Returns a heap-allocated struct
 * whose pointer fits in el_val_t. */
el_val_t  now_in(el_val_t cal);
el_val_t  in_calendar(el_val_t inst, el_val_t cal);
el_val_t  cal_format(el_val_t ct, el_val_t pattern);
el_val_t  cal_to_instant(el_val_t ct);
el_val_t  cal_cycle_phase(el_val_t ct);
el_val_t  cal_in(el_val_t ct, el_val_t cal);

/* LocalDate / LocalTime / LocalDateTime — calendar-agnostic value types.
 * LocalTime carries nanoseconds since midnight as a signed int64 directly
 * in the el_val_t slot (no allocation). LocalDate / LocalDateTime are
 * heap-allocated structs with magic headers. */
el_val_t  local_date(el_val_t y, el_val_t m, el_val_t d);
el_val_t  local_time(el_val_t h, el_val_t m, el_val_t s, el_val_t ns);
el_val_t  local_datetime(el_val_t date, el_val_t time);
el_val_t  zoned(el_val_t date, el_val_t time, el_val_t cal);

el_val_t  local_date_year(el_val_t ld);
el_val_t  local_date_month(el_val_t ld);
el_val_t  local_date_day(el_val_t ld);
el_val_t  local_time_hour(el_val_t lt);
el_val_t  local_time_minute(el_val_t lt);
el_val_t  local_time_second(el_val_t lt);
el_val_t  local_time_nanos(el_val_t lt);

el_val_t  el_local_date_add_dur(el_val_t ld, el_val_t dur);
el_val_t  el_local_time_add_dur(el_val_t lt, el_val_t dur);
el_val_t  el_local_date_lt(el_val_t a, el_val_t b);
el_val_t  el_local_date_eq(el_val_t a, el_val_t b);

/* Rhythm — pluggable recurrence AST. Returns a heap-allocated struct
 * pointer in el_val_t; rhythms are immutable so callers may share them. */
el_val_t  rhythm_cycle_start(void);
el_val_t  rhythm_cycle_phase(el_val_t phase);
el_val_t  rhythm_duration(el_val_t d);
el_val_t  rhythm_session_start(void);
el_val_t  rhythm_event(el_val_t name);
el_val_t  rhythm_and(el_val_t a, el_val_t b);
el_val_t  rhythm_or(el_val_t a, el_val_t b);
el_val_t  rhythm_weekday(el_val_t day);
el_val_t  rhythm_weekly_at(el_val_t day, el_val_t hour, el_val_t minute);
el_val_t  rhythm_next_after(el_val_t r, el_val_t after, el_val_t cal);
el_val_t  rhythm_matches(el_val_t r, el_val_t ct);

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

el_val_t  uuid_new(void);
el_val_t  uuid_v4(void);

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

el_val_t  env(el_val_t key);

/* ── In-process state K/V ────────────────────────────────────────────────── */

el_val_t  state_set(el_val_t key, el_val_t value);
el_val_t  state_get(el_val_t key);
el_val_t  state_del(el_val_t key);
el_val_t  state_keys(void);
el_val_t  state_has(el_val_t key);
el_val_t  state_get_or(el_val_t key, el_val_t default_val);

/* ── Float formatting ────────────────────────────────────────────────────── */

el_val_t  float_to_str(el_val_t f);
el_val_t  int_to_float(el_val_t n);
el_val_t  float_to_int(el_val_t f);
el_val_t  format_float(el_val_t f, el_val_t decimals);
el_val_t  decimal_round(el_val_t f, el_val_t decimals);
el_val_t  str_to_float(el_val_t s);

/* ── Math (Float-aware) ──────────────────────────────────────────────────── */

el_val_t  math_sqrt(el_val_t f);
el_val_t  math_log(el_val_t f);
el_val_t  math_ln(el_val_t f);
el_val_t  math_sin(el_val_t f);
el_val_t  math_cos(el_val_t f);
el_val_t  math_pi(void);

/* ── String additions ────────────────────────────────────────────────────── */

el_val_t  str_index_of(el_val_t s, el_val_t sub);
el_val_t  str_split(el_val_t s, el_val_t sep);
el_val_t  str_char_at(el_val_t s, el_val_t i);
el_val_t  str_char_code(el_val_t s, el_val_t i);
el_val_t  str_pad_left(el_val_t s, el_val_t width, el_val_t pad);
el_val_t  str_pad_right(el_val_t s, el_val_t width, el_val_t pad);
el_val_t  str_format(el_val_t fmt, el_val_t data);
el_val_t  str_lower(el_val_t s);
el_val_t  str_upper(el_val_t s);

/* ── Text-processing primitives (Phase 1: byte/codepoint, ASCII char classes)
 * Phase 2 (filed): Unicode-grapheme awareness, NFC/NFD normalization, regex.
 * is_* predicates: empty input returns false; multi-char requires ALL bytes
 * to match. ASCII ranges only in Phase 1. */

/* Counting */
el_val_t  str_count(el_val_t s, el_val_t sub);              /* non-overlapping */
el_val_t  str_count_chars(el_val_t s);                      /* codepoint count */
el_val_t  str_count_bytes(el_val_t s);                      /* alias of str_len */
el_val_t  str_count_lines(el_val_t s);
el_val_t  str_count_words(el_val_t s);
el_val_t  str_count_letters(el_val_t s);                    /* ASCII [A-Za-z] */
el_val_t  str_count_digits(el_val_t s);                     /* ASCII [0-9] */

/* Find / position */
el_val_t  str_index_of_all(el_val_t s, el_val_t sub);       /* [Int] of byte offsets */
el_val_t  str_last_index_of(el_val_t s, el_val_t sub);
el_val_t  str_find_chars(el_val_t s, el_val_t any_of);      /* first idx of any ch */

/* Transform */
el_val_t  str_repeat(el_val_t s, el_val_t n);
el_val_t  str_reverse(el_val_t s);                          /* by codepoint */
el_val_t  str_strip_prefix(el_val_t s, el_val_t prefix);
el_val_t  str_strip_suffix(el_val_t s, el_val_t suffix);
el_val_t  str_strip_chars(el_val_t s, el_val_t chars);
el_val_t  str_lstrip(el_val_t s);
el_val_t  str_rstrip(el_val_t s);

/* Char classification (Bool) */
el_val_t  is_letter(el_val_t s);
el_val_t  is_digit(el_val_t s);
el_val_t  is_alphanumeric(el_val_t s);
el_val_t  is_whitespace(el_val_t s);
el_val_t  is_punctuation(el_val_t s);
el_val_t  is_uppercase(el_val_t s);
el_val_t  is_lowercase(el_val_t s);

/* Split / join */
el_val_t  str_split_lines(el_val_t s);
el_val_t  str_split_chars(el_val_t s);                      /* alias of native_string_chars */
el_val_t  str_split_n(el_val_t s, el_val_t sep, el_val_t n);
el_val_t  str_join(el_val_t list, el_val_t sep);            /* alias of list_join */

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

el_val_t  list_push(el_val_t list, el_val_t elem);
el_val_t  list_push_front(el_val_t list, el_val_t elem);
el_val_t  list_join(el_val_t list, el_val_t sep);
el_val_t  list_range(el_val_t start, el_val_t end);

/* ── Bool helpers ────────────────────────────────────────────────────────── */

el_val_t  bool_to_str(el_val_t b);

/* ── Numeric parsing ─────────────────────────────────────────────────────── */

el_val_t  parse_int(el_val_t s, el_val_t default_val);

/* ── Process ─────────────────────────────────────────────────────────────── */

el_val_t  exit_program(el_val_t code);
el_val_t  getpid_now(void);

/* Self-terminating memory guard. Reads ELC_MAX_MEM_MB (default 512) and
 * exits with code 1 if resident memory exceeds the limit. Call periodically
 * during long compilation loops (e.g. after each function is compiled).
 * Returns 0 when memory is within bounds.                                   */
el_val_t  el_mem_check(void);

/* ── CGI identity ─────────────────────────────────────────────────────────────
 * Called at the start of main() in CGI programs (those with a `cgi {}` block).
 * Records the program's DHARMA identity before any other code executes.      */

void      el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
                      el_val_t network, el_val_t engram);

/* ── DHARMA network builtins ─────────────────────────────────────────────────
 * Available to CGI programs (declared with a `cgi {}` block).
 *
 * Peers are addressed by `dharma_id` of the form
 *     "<registry-id>@<transport-url>"   e.g. "ntn-genesis@http://localhost:7770"
 * If the @<url> portion is omitted, transport defaults to
 * "http://localhost:7770" (the local CGI daemon assumption).
 *
 * Wire protocol (all peers expose):
 *   POST <url>/dharma/recv      { channel, from, content } → response body
 *   POST <url>/dharma/event     { type, payload, source, timestamp }
 *   POST <url>/api/activate     { query }                 → list of nodes
 *
 * Hosting application's responsibility: an El program with a `cgi {}` block
 * runs http_serve() with its own request handler; that handler should route
 * "/dharma/event" requests by calling el_runtime_dharma_event_arrive() so
 * incoming events feed dharma_field() queues. The runtime itself does not
 * intercept any /dharma path.                                                */

el_val_t  dharma_connect(el_val_t cgi_id);
el_val_t  dharma_send(el_val_t channel, el_val_t content);
el_val_t  dharma_activate(el_val_t query);
void      dharma_emit(el_val_t event_type, el_val_t payload);
el_val_t  dharma_field(el_val_t event_type);
void      dharma_strengthen(el_val_t cgi_id, el_val_t weight);
el_val_t  dharma_relationship(el_val_t cgi_id);
el_val_t  dharma_peers(void);

/* Public C API: called by an El program's HTTP handler when a /dharma/event
 * request arrives. Pushes onto the per-event-type queue and signals any
 * pending dharma_field() blockers. All three arguments must be NUL-terminated
 * C strings (or NULL — then treated as empty).                               */
void      el_runtime_dharma_event_arrive(const char* event_type,
                                         const char* payload,
                                         const char* source);

/* ── Engram local graph primitives ───────────────────────────────────────────
 * Operate on the CGI's local Engram knowledge graph.
 * `engram_activate` queries the local graph only; `dharma_activate` is
 * network-wide across all connected CGI graphs.                              */

el_val_t  engram_node(el_val_t content, el_val_t node_type, el_val_t salience);
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);
/* Layered consciousness — see el_runtime.c for the layered architecture
 * design notes (search "Layered consciousness architecture"). The five
 * canonical layers (safety / core-identity / domain-knowledge / imprint /
 * suit) are seeded automatically; engram_add_layer extends the registry
 * with imprint or suit overlays at runtime. Nodes default to layer 1
 * (core-identity) when created via engram_node / engram_node_full. */
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 status, el_val_t tags, el_val_t layer_id);
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);
el_val_t  engram_remove_layer(el_val_t layer_id);
el_val_t  engram_list_layers(void);
el_val_t  engram_get_node(el_val_t id);
void      engram_strengthen(el_val_t node_id);
void      engram_forget(el_val_t node_id);
el_val_t  engram_node_count(void);
el_val_t  engram_search(el_val_t query, el_val_t limit);
el_val_t  engram_scan_nodes(el_val_t limit, el_val_t offset);
void      engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation);
el_val_t  engram_edge_between(el_val_t from_id, el_val_t to_id);
el_val_t  engram_neighbors(el_val_t node_id);
el_val_t  engram_neighbors_filtered(el_val_t node_id, el_val_t max_depth, el_val_t direction);
el_val_t  engram_edge_count(void);
/* Three-pass activation: background fan-out → working-memory promotion →
 * Layer 0 override. See "Three-pass activation" in el_runtime.c. */
el_val_t  engram_activate(el_val_t query, el_val_t depth);
el_val_t  engram_save(el_val_t path);
el_val_t  engram_load(el_val_t path);

/* JSON-string accessors — return pre-serialized JSON so HTTP handlers
 * can pass results straight through without round-tripping ElList/ElMap
 * through json_stringify. */
el_val_t  engram_get_node_json(el_val_t id);
el_val_t  engram_search_json(el_val_t query, el_val_t limit);
el_val_t  engram_scan_nodes_json(el_val_t limit, el_val_t offset);
el_val_t  engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
el_val_t  engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
el_val_t  engram_activate_json(el_val_t query, el_val_t depth);
el_val_t  engram_stats_json(void);
el_val_t  engram_list_layers_json(void);
/* engram_compile_layered_json — produce a prompt-ready text block split
 * into "[LAYER 0 — STRUCTURAL]" (non-suppressible layers, sacred fire)
 * and "[ENGRAM CONTEXT]" (standard suppressible layers). Returns "" if
 * no nodes promoted to working memory. */
el_val_t  engram_compile_layered_json(el_val_t intent, el_val_t depth);

/* ── LLM (Anthropic API client) ─────────────────────────────────────────────
 * All functions call https://api.anthropic.com/v1/messages with the API key
 * from env ANTHROPIC_API_KEY. Default model when empty: claude-sonnet-4-5. */

el_val_t  llm_call(el_val_t model, el_val_t prompt);
el_val_t  llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt);
el_val_t  llm_call_agentic(el_val_t model, el_val_t system, el_val_t user, el_val_t tools);
el_val_t  llm_vision(el_val_t model, el_val_t system, el_val_t prompt, el_val_t image_url_or_b64);
el_val_t  llm_models(void);

/* Register a tool handler by name. The handler is looked up via dlsym
 * (mirroring http_set_handler), so any El `fn <name>(input)` compiles to
 * a global C symbol that this function can locate at runtime.
 * Handler signature: `el_val_t handler(el_val_t input_json)` — receives
 * the tool input as a JSON-string el_val_t and returns a JSON-string
 * el_val_t result. Used by llm_call_agentic.                                 */
void      llm_register_tool(el_val_t name, el_val_t handler_fn_name);

/* ── args() ─────────────────────────────────────────────────────────────────
 * Provides access to command-line arguments passed to the program.
 * Populated by el_runtime_init_args() before main() runs.           */

el_val_t  args(void);
void      el_runtime_init_args(int argc, char** argv);

/* ── Crypto primitives ─────────────────────────────────────────────────────
 * SHA-256, HMAC-SHA-256, and base64 (standard + URL-safe).
 * Self-contained — no OpenSSL/libcrypto dependency. The implementations are
 * adapted from public-domain reference code (Brad Conte / RFC 4648).
 *
 * Bytes-returning variants (sha256_bytes, hmac_sha256_bytes) return a string
 * value whose contents are raw binary; callers usually feed these into
 * base64_encode. Note that el_val_t strings are NUL-terminated by convention,
 * so the binary payload may contain embedded NULs — pass it directly into
 * base64_encode (which uses an explicit length) rather than treating it as
 * a printable C string.
 *
 * The "base64" variants emit/accept RFC 4648 standard alphabet with padding.
 * The "base64url" variants use URL-safe alphabet (`-`/`_`) with no padding,
 * as used in JWTs.                                                           */

el_val_t  sha256_hex(el_val_t input);
el_val_t  sha256_bytes(el_val_t input);
el_val_t  hmac_sha256_hex(el_val_t key, el_val_t message);
el_val_t  hmac_sha256_bytes(el_val_t key, el_val_t message);
el_val_t  base64_encode(el_val_t input);
el_val_t  base64_decode(el_val_t input);
el_val_t  base64url_encode(el_val_t input);
el_val_t  base64url_decode(el_val_t input);

/* Length-aware variants (internal — exposed for the rare caller that already
 * has a known-length binary buffer and doesn't want to round-trip through
 * a NUL-terminated el_val_t string). Sha256_bytes and hmac_sha256_bytes feed
 * these implicitly. */
el_val_t  el_sha256_bytes_n(const unsigned char* data, size_t len);
el_val_t  el_base64_encode_n(const unsigned char* data, size_t len, int url_safe);

/* ── Post-quantum primitives (liboqs-backed) ────────────────────────────────
 * All inputs/outputs hex-encoded. Algorithm choices:
 *   Signature: CRYSTALS-Dilithium-3   (NIST level 3, balanced)
 *   KEM:       CRYSTALS-Kyber-768     (NIST level 3)
 *   Hash:      SHA3-256 (Keccak)      (PQ-aware protocols favour SHA3 over SHA2)
 *
 * If liboqs is not linked (detected via __has_include(<oqs/oqs.h>) at compile
 * time), the pq_* entry points return a JSON-shaped error string so callers
 * fail loudly rather than silently fall back to classical schemes:
 *   {"error":"liboqs not linked, post-quantum primitives unavailable"}
 *
 * The hybrid handshake pairs X25519 with Kyber-768 per NIST PQ guidance and
 * CNSA 2.0. Combined shared secret is HKDF-SHA256(x25519_ss || kyber_ss).
 * Even if Kyber falls, X25519 holds; if X25519 falls under quantum attack,
 * Kyber holds. SHA3-256 also remains usable independent of liboqs (the
 * Keccak permutation is PQ-OK as a primitive). */

el_val_t  pq_keygen_signature(void);
el_val_t  pq_sign(el_val_t secret_key_hex, el_val_t message);
el_val_t  pq_verify(el_val_t public_key_hex, el_val_t message, el_val_t signature_hex);

el_val_t  pq_kem_keygen(void);
el_val_t  pq_kem_encaps(el_val_t public_key_hex);
el_val_t  pq_kem_decaps(el_val_t secret_key_hex, el_val_t ciphertext_hex);

el_val_t  pq_hybrid_keygen(void);
el_val_t  pq_hybrid_handshake(el_val_t remote_pub_combined);

el_val_t  sha3_256_hex(el_val_t input);

/* ── AEAD: AES-256-GCM (libcrypto-backed) ───────────────────────────────────
 * Symmetric authenticated encryption used to wrap envelopes after a KEM
 * handshake. Caller MUST supply a 32-byte key (64 hex chars) — typically the
 * Kyber-768 / hybrid shared_secret, optionally normalized via SHA3-256.
 *
 * aead_encrypt returns a JSON map {"nonce":"...","ciphertext":"..."} where
 * ciphertext is the AES-256-GCM output with the 16-byte auth tag appended.
 * Nonce is a fresh 12-byte CSPRNG draw — callers never pick the nonce, which
 * structurally rules out the GCM nonce-reuse footgun.
 *
 * aead_decrypt returns the plaintext String, or "" on any failure (including
 * auth-tag mismatch). Callers MUST check for "" before trusting the result. */
el_val_t  aead_encrypt(el_val_t key_hex, el_val_t plaintext);
el_val_t  aead_decrypt(el_val_t key_hex, el_val_t nonce_hex, el_val_t ciphertext_hex);

/* ── Native VM builtin aliases (for compiled El source) ─────────────────────
 * These match the El VM's native_* builtins so that El source compiled
 * to C can call the same names without modification.                */

el_val_t  native_list_get(el_val_t list, el_val_t index);
el_val_t  native_list_len(el_val_t list);
el_val_t  native_list_append(el_val_t list, el_val_t elem);
el_val_t  native_list_empty(void);
el_val_t  native_list_clone(el_val_t list);
el_val_t  native_string_chars(el_val_t s);
el_val_t  native_int_to_str(el_val_t n);

/* ── Method-call shorthand aliases ──────────────────────────────────────────
 * The El method-call convention `obj.method(args)` compiles to
 * `method(obj, args)`.  These aliases expose the runtime functions under
 * the short names that result from method calls in El source.
 *
 * Example: `myList.append(x)` → `append(myList, x)` (calls this alias)
 *          `myList.len()`     → `len(myList)`         (calls this alias)  */

el_val_t  append(el_val_t list, el_val_t elem);   /* el_list_append */
el_val_t  len(el_val_t list);                      /* el_list_len    */
el_val_t  get(el_val_t list, el_val_t index);      /* el_list_get    */
el_val_t  map_get(el_val_t map, el_val_t key);     /* el_map_get     */
el_val_t  map_set(el_val_t map, el_val_t key, el_val_t value); /* el_map_set */

/* ── OTLP/HTTP Observability ─────────────────────────────────────────────── */
/* See bottom of el_runtime.c for the implementation.
 * Configured by env vars OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_SERVICE_VERSION.
 * No-op when OTLP_ENDPOINT is unset. Drop-on-failure semantics. */
/* ── Subprocess execution ────────────────────────────────────────────────── */
el_val_t exec_command(el_val_t cmd);   /* run shell command, return exit code */
el_val_t exec_capture(el_val_t cmd);   /* run shell command, capture stdout   */
el_val_t exec(el_val_t cmd);           /* exec(cmd) → stdout String (30s timeout) */
el_val_t exec_bg(el_val_t cmd);        /* exec_bg(cmd) → PID String (non-blocking) */

/* ── Stdout redirection (used by compiler JS pipeline) ───────────────────── */
el_val_t stdout_to_file(el_val_t path); /* redirect process stdout to a file  */
el_val_t stdout_restore(void);          /* restore process stdout to terminal  */

el_val_t emit_log(el_val_t level, el_val_t msg, el_val_t fields_json);
el_val_t emit_metric(el_val_t name, el_val_t value, el_val_t tags_json);
el_val_t trace_span_start(el_val_t name);
el_val_t trace_span_end(el_val_t span_handle);
el_val_t emit_event(el_val_t name, el_val_t duration_ms);

el_val_t __thread_create(el_val_t fn_name_v, el_val_t arg_v);
el_val_t __thread_join(el_val_t tid_v);

/* ── __ prefixed aliases (self-hosting compiler ABI) ─────────────────────────
 * The El self-hosting compiler emits calls to __-prefixed names. These are
 * forwarding wrappers around the existing el_runtime functions above.        */

/* I/O */
el_val_t  __println(el_val_t s);
el_val_t  __print(el_val_t s);
el_val_t  __readline(void);

/* String */
el_val_t  __int_to_str(el_val_t n);
el_val_t  __str_to_int(el_val_t s);
el_val_t  __float_to_str(el_val_t f);
el_val_t  __str_to_float(el_val_t s);
el_val_t  __str_len(el_val_t s);
el_val_t  __str_char_at(el_val_t s, el_val_t i);
el_val_t  __str_cmp(el_val_t a, el_val_t b);
el_val_t  __str_ncmp(el_val_t a, el_val_t b, el_val_t n);
el_val_t  __str_concat_raw(el_val_t a, el_val_t b);
el_val_t  __str_slice_raw(el_val_t s, el_val_t start, el_val_t end);
el_val_t  __str_alloc(el_val_t n);
el_val_t  __str_set_char(el_val_t s, el_val_t i, el_val_t c);

/* URL encoding */
el_val_t  __url_encode(el_val_t s);
el_val_t  __url_decode(el_val_t s);

/* Environment */
el_val_t  __env_get(el_val_t key);

/* Subprocess */
el_val_t  __exec(el_val_t cmd);
el_val_t  __exec_bg(el_val_t cmd);

/* Process */
el_val_t  __exit_program(el_val_t code);

/* Filesystem */
el_val_t  __fs_exists(el_val_t path);
el_val_t  __fs_mkdir(el_val_t path);
el_val_t  __fs_read(el_val_t path);
el_val_t  __fs_write(el_val_t path, el_val_t content);
el_val_t  __fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t n);
el_val_t  __fs_list_raw(el_val_t path);

/* HTTP server */
el_val_t  __http_response(el_val_t status, el_val_t headers_json, el_val_t body);
el_val_t  __http_serve(el_val_t port, el_val_t handler);
el_val_t  __http_serve_v2(el_val_t port, el_val_t handler);

/* HTTP conn fd / SSE (weak; overridden by el_seed.c when linked together) */
el_val_t  __http_conn_fd(void);
el_val_t  __http_sse_open(el_val_t conn_id);
el_val_t  __http_sse_send(el_val_t conn_id, el_val_t data);
el_val_t  __http_sse_close(el_val_t conn_id);

/* HTTP client (requires HAVE_CURL; stubs provided for no-curl builds) */
el_val_t  __http_do(el_val_t method, el_val_t url, el_val_t body,
                    el_val_t headers_map, el_val_t timeout_ms);
el_val_t  __http_do_map(el_val_t method, el_val_t url, el_val_t body,
                        el_val_t headers_json, el_val_t timeout_ms);
el_val_t  __http_do_map_to_file(el_val_t method, el_val_t url, el_val_t body,
                                el_val_t headers_json, el_val_t output_path);

/* JSON */
el_val_t  __json_array_get(el_val_t json, el_val_t index);
el_val_t  __json_array_get_string(el_val_t json, el_val_t index);
el_val_t  __json_array_len(el_val_t json);
el_val_t  __json_get(el_val_t json, el_val_t key);
el_val_t  __json_get_raw(el_val_t json, el_val_t key);
el_val_t  __json_set(el_val_t json, el_val_t key, el_val_t value);
el_val_t  __json_parse_map(el_val_t json_str);
el_val_t  __json_stringify_val(el_val_t val);

/* Hashing */
el_val_t  __sha256_hex(el_val_t s);

/* State K/V */
el_val_t  __state_del(el_val_t key);
el_val_t  __state_get(el_val_t key);
el_val_t  __state_keys(void);
el_val_t  __state_set(el_val_t key, el_val_t val);

/* UUID */
el_val_t  __uuid_v4(void);

/* Args */
el_val_t  __args_json(void);

#ifdef __cplusplus
}
#endif
