// runtime/http.el — El HTTP client and server wrappers
//
// Thin El layer over seed primitives. All network I/O is performed by the
// seed; this file provides the public API that El programs import.
//
// Seed primitives consumed:
//   __http_do(method, url, body, headers_json, timeout_ms) -> String
//   __http_do_to_file(method, url, body, headers_json, out_path) -> Bool
//   __http_do_map(method, url, body, headers_map, timeout_ms) -> String
//   __http_do_map_to_file(method, url, body, headers_map, out_path) -> Bool
//   __http_serve(port, handler_name)
//   __http_serve_v2(port, handler_name)
//   __http_response(status, headers_json, body) -> String
//   __env_get(key) -> String
//
// NOTE FOR SEED AGENT: __http_do_map and __http_do_map_to_file must be added
// to the seed. They are identical to __http_do / __http_do_to_file except
// they accept an ElMap directly for headers instead of a pre-serialised JSON
// string. This avoids needing map iteration in El (which has no for-loop or
// map iterator primitive). The seed implementation maps to headers_from_map()
// in el_runtime.c.
//
// Other builtins used:
//   str_eq(a, b) -> Bool
//   str_to_int(s) -> Int

// ── Timeout helper ────────────────────────────────────────────────────────────

// el_http_timeout_ms returns the configured HTTP timeout in milliseconds.
// Reads EL_HTTP_TIMEOUT_MS from the environment; defaults to 60000 (60s).
// Returns 60000 if the env var is absent, empty, or non-positive.
fn el_http_timeout_ms() -> Int {
    let v: String = __env_get("EL_HTTP_TIMEOUT_MS")
    if str_eq(v, "") { return 60000 }
    let n: Int = str_to_int(v)
    if n <= 0 { return 60000 }
    return n
}

// ── HTTP client — simple variants ────────────────────────────────────────────

// http_get performs an HTTP GET request and returns the response body.
// On transport failure the seed returns an error JSON fragment.
fn http_get(url: String) -> String {
    return __http_do("GET", url, "", "{}", el_http_timeout_ms())
}

// http_post performs an HTTP POST request with the given body.
// No Content-Type header is set; use http_post_json for JSON payloads.
fn http_post(url: String, body: String) -> String {
    return __http_do("POST", url, body, "{}", el_http_timeout_ms())
}

// http_post_json performs an HTTP POST request with Content-Type:
// application/json. body must be a valid JSON string.
fn http_post_json(url: String, body: String) -> String {
    return __http_do("POST", url, body, "{\"Content-Type\":\"application/json\"}", el_http_timeout_ms())
}

// http_delete performs an HTTP DELETE request and returns the response body.
fn http_delete(url: String) -> String {
    return __http_do("DELETE", url, "", "{}", el_http_timeout_ms())
}

// ── HTTP client — header map variants ────────────────────────────────────────
//
// These accept a Map<String, String> of request headers. The seed's
// __http_do_map converts the ElMap to a curl_slist internally, matching
// the headers_from_map() logic in el_runtime.c.

// http_get_with_headers performs an HTTP GET with caller-supplied headers.
fn http_get_with_headers(url: String, headers: Map<String, String>) -> String {
    return __http_do_map("GET", url, "", headers, el_http_timeout_ms())
}

// http_post_with_headers performs an HTTP POST with caller-supplied headers.
fn http_post_with_headers(url: String, body: String, headers: Map<String, String>) -> String {
    return __http_do_map("POST", url, body, headers, el_http_timeout_ms())
}

// http_post_form_auth performs an HTTP POST with
// Content-Type: application/x-www-form-urlencoded and an Authorization
// header built from auth_header (the caller passes the full header value,
// e.g. "Bearer <token>" or "Basic <base64>").
//
// Mirrors http_post_form_auth in el_runtime.c: two headers are injected,
// Content-Type is always set; Authorization is omitted when auth_header is "".
fn http_post_form_auth(url: String, form_body: String, auth_header: String) -> String {
    if str_eq(auth_header, "") {
        return __http_do("POST", url, form_body, "{\"Content-Type\":\"application/x-www-form-urlencoded\"}", el_http_timeout_ms())
    }
    return __http_do("POST", url, form_body, "{\"Content-Type\":\"application/x-www-form-urlencoded\",\"Authorization\":\"" + auth_header + "\"}", el_http_timeout_ms())
}

// ── HTTP client — streaming to file ──────────────────────────────────────────
//
// These route the response body directly to a file via the seed, bypassing
// the El string layer. This preserves embedded NUL bytes in binary payloads
// (audio, images, etc.) — an El string would truncate at the first NUL.
// Returns true on success, false on any transport or I/O error.

// http_post_to_file performs an HTTP POST and streams the response body to
// output_path. Useful for large or binary response payloads.
fn http_post_to_file(url: String, body: String, headers: Map<String, String>, output_path: String) -> Bool {
    return __http_do_map_to_file("POST", url, body, headers, output_path)
}

// http_get_to_file performs an HTTP GET and streams the response body to
// output_path. Useful for large or binary response payloads.
fn http_get_to_file(url: String, headers: Map<String, String>, output_path: String) -> Bool {
    return __http_do_map_to_file("GET", url, "", headers, output_path)
}

// ── HTTP server ───────────────────────────────────────────────────────────────
//
// El programs call http_set_handler(name) to register which El function
// handles requests, then http_serve(port, name) to start listening.
// The seed resolves handler names via dlsym — every El fn compiles to a
// global C symbol with the same name, so self-registration works without
// any El-level registry.
//
// v2 widens the handler signature from
//   (method, path, body) -> String
// to
//   (method, path, headers_map, body) -> String
// so handlers can inspect incoming headers. Use http_serve_v2 +
// http_set_handler_v2 for v2 handlers.

// http_set_handler registers name as the active v1 request handler.
// The seed resolves the symbol via dlsym at call time; no El-level
// registration is needed. This is a no-op at the El layer.
fn http_set_handler(name: String) {
    // no-op: the seed handles handler registration via dlsym
}

// http_serve starts an HTTP/1.1 server on port, dispatching every request
// to handler (a v1 handler: fn(method, path, body) -> String).
// Blocks forever. Accepts both IPv4 and IPv6 (dual-stack).
fn http_serve(port: Int, handler: String) {
    __http_serve(port, handler)
}

// http_set_handler_v2 registers name as the active v2 request handler.
// No-op at the El layer; the seed uses dlsym.
fn http_set_handler_v2(name: String) {
    // no-op: the seed handles handler registration via dlsym
}

// http_serve_v2 starts an HTTP/1.1 server on port, dispatching every
// request to handler (a v2 handler: fn(method, path, headers, body) ->
// String). Blocks forever. Accepts both IPv4 and IPv6 (dual-stack).
fn http_serve_v2(port: Int, handler: String) {
    __http_serve_v2(port, handler)
}

// ── Response construction ─────────────────────────────────────────────────────

// http_response builds a structured response envelope that the HTTP server
// runtime unpacks into a real HTTP response with the given status code and
// headers. status must be 100–599 (defaults to 200 outside that range).
// headers_json must be a JSON object literal (e.g. "{}" or
// "{\"Content-Type\":\"text/html\"}"); body is the response body string.
//
// The envelope format is:
//   {"el_http_response":1,"status":<n>,"headers":<obj>,"body":"<escaped>"}
// The runtime detects this prefix and unpacks it; plain string returns from
// handlers are still supported and are sent as HTTP 200 with auto-detected
// Content-Type.
fn http_response(status: Int, headers_json: String, body: String) -> String {
    return __http_response(status, headers_json, body)
}

// ── HTTP client — PATCH ───────────────────────────────────────────────────────

// http_patch performs an HTTP PATCH request with Content-Type: application/json.
fn http_patch(url: String, body: String) -> String {
    return __http_do("PATCH", url, body, "{\"Content-Type\":\"application/json\"}", el_http_timeout_ms())
}

// ── HTTP client — Engram variants (optional API key) ──────────────────────────
//
// These are used by dharma's db.el to talk to Engram nodes.
// The key parameter is the X-API-Key header value; pass "" for no auth.

// http_post_engram performs an HTTP POST with Content-Type: application/json
// and an optional X-API-Key header. If key is "" no auth header is added.
fn http_post_engram(url: String, key: String, body: String) -> String {
    if str_eq(key, "") {
        return __http_do("POST", url, body, "{\"Content-Type\":\"application/json\"}", el_http_timeout_ms())
    }
    return __http_do("POST", url, body, "{\"Content-Type\":\"application/json\",\"X-API-Key\":\"" + key + "\"}", el_http_timeout_ms())
}

// http_get_engram performs an HTTP GET with an optional X-API-Key header.
fn http_get_engram(url: String, key: String) -> String {
    if str_eq(key, "") {
        return __http_do("GET", url, "", "{}", el_http_timeout_ms())
    }
    return __http_do("GET", url, "", "{\"X-API-Key\":\"" + key + "\"}", el_http_timeout_ms())
}

// ── SSE — Server-Sent Events streaming ───────────────────────────────────────
//
// Usage pattern for an SSE handler:
//
//   fn my_handler(method: String, path: String, headers: Map<String, String>, body: String) -> String {
//       let fd: Int = http_conn_fd()
//       http_sse_open(fd)
//       http_sse_send(fd, "hello")
//       http_sse_send(fd, "world")
//       http_sse_close(fd)
//       return http_sse_sentinel()
//   }
//
// The sentinel return value tells http_serve_v2 NOT to close the connection
// automatically — the handler already closed it via http_sse_close.

// http_conn_fd returns the raw file descriptor for the current HTTP connection.
// Only valid inside an http_serve_v2 handler, before the handler returns.
// Use with http_sse_open / http_sse_send / http_sse_close for streaming.
fn http_conn_fd() -> Int {
    return __http_conn_fd()
}

// http_sse_open sends SSE response headers on the current connection,
// keeping it open for streaming. Call once at the start of an SSE handler.
// Returns true on success.
fn http_sse_open(fd: Int) -> Bool {
    return __http_sse_open(fd)
}

// http_sse_send writes one SSE event to the connection.
// data should not contain newlines (they are added automatically).
// Returns true if the write succeeded (client still connected).
fn http_sse_send(fd: Int, data: String) -> Bool {
    return __http_sse_send(fd, data)
}

// http_sse_close closes the SSE connection.
fn http_sse_close(fd: Int) {
    __http_sse_close(fd)
    return
}

// http_sse_sentinel is the return value an SSE handler must return
// to tell the HTTP server NOT to close the connection automatically.
// The handler takes ownership of the fd and closes it via http_sse_close.
fn http_sse_sentinel() -> String {
    return "__sse__"
}
