// Generated by `aver compile --pack cloudflare` — bootstrap that
// satisfies user.wasm's `aver/*` imports inside the Cloudflare
// Workers JS environment. Edit freely; regenerating will overwrite
// this file.
//
// `--preset cloudflare` compiles with `--target wasm`, which uses
// `wasm-merge` to inline the Aver runtime (alloc, GC, HAMT,
// string/list/vector ops) directly into user.wasm. Cloudflare Workers
// reject `WebAssembly.instantiate(bytes, …)` from arbitrary fetched
// bytes, so the single-bundled shape is the only viable path here —
// browsers / Deno / Bun can keep the thinner `--target edge-wasm`
// shape with a runtime fetched from averlang.dev/runtime/.

import userWasm from "./__WASM_NAME__.wasm";

// Module-scope handles populated when the user instance comes up.
// Host-side helpers reach for `userExports.memory` / `.alloc` /
// `.rt_map_from_list` (the merged runtime exports them through
// the same wasm module). `pending` carries per-request state the
// fetch-bridge host imports read and write.
let userInstance = null;
let userMemory = null;
let userExports = null;
let pending = null;

// Cached typed-array views over guest linear memory. JS `ArrayBuffer`
// objects are detached (not just resized) when wasm calls
// `memory.grow`, so any `DataView` / `Uint8Array` built before the
// grow becomes unusable. We keep `cachedBuffer` as the identity of
// the last buffer we saw; if it differs from `userMemory.buffer`
// the views get rebuilt. Same pattern wasm-bindgen uses for its
// generated shims; saves a `new DataView` + `new Uint8Array` per
// host import on the hot path.
let cachedBuffer = null;
let cachedDataView = null;
let cachedUint8 = null;

function refreshViews() {
  if (cachedBuffer !== userMemory.buffer) {
    cachedBuffer = userMemory.buffer;
    cachedDataView = new DataView(cachedBuffer);
    cachedUint8 = new Uint8Array(cachedBuffer);
  }
}

const encoder = new TextEncoder();
const decoder = new TextDecoder("utf-8");

function writeAverString(text) {
  // Build an OBJ_STRING in guest memory: 8-byte header + UTF-8 bytes.
  // Header low 32 bits = byte length; kind/tag/meta high bytes are
  // naturally zero for plain strings.
  //
  // `encodeInto` writes UTF-8 directly into linear memory, skipping
  // the intermediate `Uint8Array` `encoder.encode()` would allocate.
  // We over-allocate `length * 3` (worst-case UTF-8 for any JS char,
  // surrogate pairs already span two JS chars so the bound is safe)
  // and trust the bump allocator — for our string sizes the unused
  // slack is irrelevant.
  const upper = text.length * 3;
  const ptr = userExports.alloc(upper + 8);
  // Refresh AFTER alloc: the bump allocator may trigger memory.grow
  // which detaches the previous ArrayBuffer; cached views built
  // before the grow would be unusable.
  refreshViews();
  const { written } = encoder.encodeInto(
    text,
    cachedUint8.subarray(ptr + 8, ptr + 8 + upper),
  );
  // Header as two i32 writes — avoids the BigInt round-trip a
  // `setBigUint64` would force. The high 4 bytes (kind/tag/meta)
  // are zero for plain OBJ_STRING; alloc returns un-zeroed bump
  // memory so we explicitly clear them.
  cachedDataView.setUint32(ptr, written, true);
  cachedDataView.setUint32(ptr + 4, 0, true);
  return ptr;
}

function readString(ptr, len) {
  refreshViews();
  return decoder.decode(cachedUint8.subarray(ptr, ptr + len));
}

function makeAverImports() {
  return {
    console_print: (ptr, len) => console.log(readString(ptr, len)),
    console_error: (ptr, len) => console.error(readString(ptr, len)),
    console_warn:  (ptr, len) => console.warn(readString(ptr, len)),
    time_unixMs:   () => BigInt(Date.now()),
    random_int: (lo, hi) => {
      const span = hi - lo + 1n;
      return lo + BigInt(Math.floor(Math.random() * Number(span)));
    },
    random_float: () => Math.random(),
    // ─── Fetch bridge: HTTP request fields & response builder ────
    // Each host import is a lazy crossing — guest pays only for
    // the fields it reads.
    request_method: () => writeAverString(pending?.req?.method ?? "GET"),
    request_url: () => writeAverString(
      pending?.req ? new URL(pending.req.url).pathname : "/",
    ),
    request_body: () => {
      // body() is sync from Aver's POV; if we already buffered it
      // we hand back the OBJ_STRING, else empty. Bootstrap pre-reads
      // the body before invoking the handler.
      return writeAverString(pending?.body ?? "");
    },
    request_headers_load: () => {
      const h = pending?.req?.headers;
      if (!h) return 0;
      return buildHeadersMap(h);
    },
    response_text: (status, ptr, len) => {
      // Stash response data; native Response built post-handler. The
      // headers list is populated by `response_set_header` calls
      // emitted in the HttpResponse construction lowering — those
      // fire before this finalizer so they're already on the pending
      // record by the time we land here.
      const existingHeaders = pending.response?.headers ?? [];
      pending.response = {
        status,
        body: readString(ptr, len),
        headers: existingHeaders,
      };
      // Return a non-zero sentinel so user code that checks the
      // handle for truthiness is happy.
      return 1;
    },
    response_set_header: (namePtr, nameLen, valuePtr, valueLen) => {
      // Append `(name, value)` to the pending Response's headers.
      // We push individual pairs (rather than a Map) so multi-value
      // headers — Set-Cookie, Vary, … — preserve order and don't
      // collide.
      if (!pending.response) pending.response = { headers: [] };
      if (!pending.response.headers) pending.response.headers = [];
      pending.response.headers.push([
        readString(namePtr, nameLen),
        readString(valuePtr, valueLen),
      ]);
    },
    // ─── Env ───────────────────────────────────────────────
    // Workers' `env` is the bag of bindings + secrets passed to
    // `fetch(request, env, ctx)`. We stash it on `pending.env` for
    // the duration of the handler call (see the fetch entrypoint
    // below). Returns -1 (Aver's NONE_SENTINEL) when the name
    // isn't bound, otherwise allocates a guest OBJ_STRING.
    env_get: (namePtr, nameLen) => {
      const name = readString(namePtr, nameLen);
      const value = pending?.env?.[name];
      if (typeof value !== "string") return -1;
      return writeAverString(value);
    },
    // Workers `env` is read-only — `env_set` is a no-op so the
    // typed contract `Env.set: (String, String) -> Unit` still
    // holds without lying about persistence.
    env_set: (_namePtr, _nameLen, _valuePtr, _valueLen) => {},
    // ─── Http (client) ──────────────────────────────────────
    // `Http.<verb>(...)` is a synchronous-looking wasm call that
    // really has to `await fetch(...)` on the host. We bridge the
    // sync↔async gap with the JS Promise Integration (JSPI)
    // proposal: each suspending import is wrapped via
    // `WebAssembly.Suspending(asyncFn)`, and the exported handler
    // is wrapped with `WebAssembly.promising(wasmFn)` so a
    // top-level `await` chains the suspension all the way back to
    // the worker's `fetch` entrypoint.
    //
    // Workers / Deno / Bun ship V8 / SpiderMonkey builds with JSPI
    // enabled; if you target a runtime that doesn't have it,
    // replace these with synchronous stubs that return a transport
    // error.
    //
    // Request headers travel over a host-maintained pending list:
    //   `http_clear_request_headers` resets it before each call,
    //   `http_add_request_header(name, value)` appends an entry
    //    (multi-value friendly via repeated calls),
    //   `http_send(method, url, body, contentType)` consumes the
    //    pending list, executes the fetch, returns
    //    `(status, body, err)`.
    http_clear_request_headers: () => {
      pending.requestHeaders = [];
    },
    http_add_request_header: (namePtr, nameLen, valuePtr, valueLen) => {
      if (!pending.requestHeaders) pending.requestHeaders = [];
      pending.requestHeaders.push([
        readString(namePtr, nameLen),
        readString(valuePtr, valueLen),
      ]);
    },
    http_send: makeSuspendingHttpSend(),
    // ─── Print/Format value ─────────────────────────────────────
    // The compiler imports these unconditionally: `print_value`
    // backs `Console.print(non-string)` and the format-debug paths,
    // `format_value` backs string interpolation when an argument
    // isn't already a String (it returns the (ptr, len) of an
    // OBJ_STRING describing the value). Stub bodies just route
    // primitives to their natural string forms; richer formatting
    // (records, lists with shape) is on the host runtime to expand.
    print_value: (tag, val) => console.log(formatValueForHost(tag, val)),
    format_value: (tag, val) => {
      const text = formatValueForHost(tag, val);
      const ptr = writeAverString(text);
      // Two-result return — fetch-bridge ABI declares (ptr, len).
      // The OBJ_STRING handle's bytes start at ptr+8; the i32 length
      // lives in the header low 32 bits. writeAverString already
      // refreshed cachedDataView so we can read straight from it.
      const len = cachedDataView.getUint32(ptr, true);
      return [ptr + 8, len];
    },
  };
}

// JSPI-suspending implementation of `aver/http_send`. The wasm
// guest calls this synchronously; control suspends here while we
// `await fetch(...)`, then resumes with the (status, body, err)
// triple on the wasm stack. `WebAssembly.Suspending` is supported
// out of the box in Cloudflare Workers / Deno / Bun / modern
// browsers; runtimes without JSPI should swap this for a sync
// stub that returns a transport error.
function makeSuspendingHttpSend() {
  if (typeof WebAssembly.Suspending !== "function") {
    // No JSPI — fall back to a sync stub that always reports a
    // transport failure. Programs branch on `Result.Err` instead
    // of crashing on a missing import.
    return (_m, _ml, _u, _ul, _b, _bl, _c, _cl) => {
      const err = writeAverString(
        "Http.send: host has no JSPI; cannot await fetch synchronously",
      );
      return [0n, 0, 0, err];
    };
  }
  return new WebAssembly.Suspending(async (
    methodPtr, methodLen, urlPtr, urlLen,
    bodyPtr, bodyLen, ctPtr, ctLen,
  ) => {
    const method = readString(methodPtr, methodLen);
    const url = readString(urlPtr, urlLen);
    const body = bodyLen > 0 ? readString(bodyPtr, bodyLen) : null;
    const contentType = ctLen > 0 ? readString(ctPtr, ctLen) : null;

    const init = { method };
    const headers = new Headers();
    if (contentType) headers.set("content-type", contentType);
    for (const [name, value] of pending.requestHeaders ?? []) {
      headers.append(name, value);
    }
    if ([...headers].length > 0) init.headers = headers;
    if (body !== null && method !== "GET" && method !== "HEAD") {
      init.body = body;
    }

    try {
      const resp = await fetch(url, init);
      const respBody = await resp.text();
      const bodyHandle = writeAverString(respBody);
      // Bulk-transfer the upstream response headers into a guest
      // `Map<String, List<String>>` so Aver code can read
      // `resp.headers` (Content-Type, Set-Cookie, rate-limit
      // headers, ETag, …). Empty Map (`0`) when the upstream
      // sent nothing — same shape Aver code already handles.
      const headersHandle = buildHeadersMap(resp.headers);
      return [BigInt(resp.status), bodyHandle, headersHandle, 0];
    } catch (e) {
      const msg = e?.message ?? String(e);
      const errHandle = writeAverString(msg);
      return [0n, 0, 0, errHandle];
    }
  });
}

// Aver runtime kind tags / wrap codes the host needs when
// hand-building heap objects (OBJ_LIST_CONS for cons cells,
// OBJ_TUPLE for (name, values) entries). Mirrors
// `src/codegen/wasm/value.rs`.
const OBJ_LIST_CONS = 4n;
const OBJ_TUPLE = 7n;
const KIND_STR = 3;

// Build an `OBJ_LIST_CONS` cell at a fresh allocation. Layout
// matches `rt_list_cons` in the runtime: 8-byte header + 8-byte
// head value (i64) + 8-byte tail (i32 sign-extended into an i64
// slot).
function consCell(headValue, tail, headPtrFlag) {
  // Header: kind << 56 | head_ptr_flag << 32 | field_count(2).
  // Decompose into two i32 writes — skips the BigInt construction
  // we'd otherwise pay for the kind constant and the metadata.
  // High 4 bytes: kind(8 bits) << 24 | tag(0) << 16 | meta(low 16).
  // Low 4 bytes: field_count.
  const ptr = userExports.alloc(24);
  refreshViews(); // alloc may have triggered memory.grow
  const high = (Number(OBJ_LIST_CONS) << 24) | (headPtrFlag & 0xffff);
  cachedDataView.setUint32(ptr, 2, true);
  cachedDataView.setUint32(ptr + 4, high, true);
  cachedDataView.setBigUint64(ptr + 8, headValue, true);
  cachedDataView.setInt32(ptr + 16, tail | 0, true);
  cachedDataView.setInt32(ptr + 20, 0, true);
  return ptr;
}

// Build an `OBJ_TUPLE` of two heap pointers — used for the
// (name, values_list) entries that `rt_map_from_list` consumes.
function tupleStrList(namePtr, valuesListPtr) {
  // Same i32 split as consCell. Meta = 0b11 (both fields are heap
  // ptrs) lives in the low 16 bits of the high word.
  const ptr = userExports.alloc(24);
  refreshViews(); // alloc may have triggered memory.grow
  const high = (Number(OBJ_TUPLE) << 24) | 0x3;
  cachedDataView.setUint32(ptr, 2, true);
  cachedDataView.setUint32(ptr + 4, high, true);
  cachedDataView.setUint32(ptr + 8, namePtr, true);
  cachedDataView.setUint32(ptr + 12, 0, true);
  cachedDataView.setUint32(ptr + 16, valuesListPtr, true);
  cachedDataView.setUint32(ptr + 20, 0, true);
  return ptr;
}

// Walk a JS `Headers` object and return a guest-side
// `Map<String, List<String>>` handle. Multi-value `Set-Cookie`
// uses `getSetCookie()` (Web Fetch API) so individual cookies
// land as separate list entries; everything else collapses
// into a single-element list (the Headers iterator already
// comma-joins same-name fields per RFC 9110 §5.3, which is the
// shape Aver code receives).
function buildHeadersMap(jsHeaders) {
  if (typeof userExports.rt_map_from_list !== "function") {
    return 0;
  }
  let listTail = 0;

  // Set-Cookie first (if available) so the resulting Map sees
  // a real `["c1", "c2", …]` list under that name, not a
  // comma-collapsed single-element value.
  let cookies = [];
  if (typeof jsHeaders.getSetCookie === "function") {
    cookies = jsHeaders.getSetCookie();
  }
  if (cookies.length > 0) {
    let valueList = 0;
    for (let i = cookies.length - 1; i >= 0; i--) {
      const cookieStr = writeAverString(cookies[i]);
      valueList = consCell(BigInt(cookieStr), valueList, 1);
    }
    const nameStr = writeAverString("set-cookie");
    const tuple = tupleStrList(nameStr, valueList);
    listTail = consCell(BigInt(tuple), listTail, 1);
  }

  // Everything else — Headers iteration yields lowercase names
  // already, so we don't have to normalise. Skip Set-Cookie since
  // we handled it above (its iterator entries are also already
  // collapsed into one comma-joined string by spec, which is wrong
  // for Set-Cookie — `getSetCookie()` is the canonical reader).
  for (const [name, value] of jsHeaders) {
    if (name.toLowerCase() === "set-cookie") continue;
    const nameStr = writeAverString(name);
    const valueStr = writeAverString(value);
    const valueList = consCell(BigInt(valueStr), 0, 1);
    const tuple = tupleStrList(nameStr, valueList);
    listTail = consCell(BigInt(tuple), listTail, 1);
  }

  return userExports.rt_map_from_list(listTail, KIND_STR, 1);
}

// Tag values follow Aver's runtime convention:
//   0 = Int (i64 raw bits in `val`)
//   1 = Float (f64 reinterpreted from `val`)
//   2 = Bool (i32 in low bits)
//   3 = String (OBJ_STRING ptr in low 32 bits — read length + bytes)
//   4 = Heap (record/list/etc. — render generic placeholder for now)
function formatValueForHost(tag, val) {
  switch (tag) {
    case 0: return String(val);            // Int
    case 1: {
      // Float — i64 bits → f64
      const buf = new ArrayBuffer(8);
      new BigInt64Array(buf)[0] = val;
      return String(new Float64Array(buf)[0]);
    }
    case 2: return Number(val) ? "true" : "false";
    case 3: {
      const ptr = Number(val & 0xffffffffn);
      if (ptr === 0) return "";
      refreshViews();
      const len = cachedDataView.getUint32(ptr, true);
      return decoder.decode(cachedUint8.subarray(ptr + 8, ptr + 8 + len));
    }
    default: return "<heap>";
  }
}

async function instantiate() {
  const imports = { aver: makeAverImports() };
  // Wrangler's `CompiledWasm` import gives a `WebAssembly.Module`,
  // not raw bytes — `WebAssembly.instantiate(module, imports)`
  // returns the `Instance` directly (not `{instance, module}`).
  const instance = await WebAssembly.instantiate(userWasm, imports);
  userInstance = instance;
  userExports = instance.exports;
  userMemory = instance.exports.memory;

  // JSPI: when Http.* effects run, the wasm frame can suspend on
  // `await fetch(...)` inside `http_send`. To let the suspension
  // unwind back to a Promise, the entry the host calls must be
  // wrapped with `WebAssembly.promising`. Stash the wrapped
  // handler on a global so the worker's `fetch` entrypoint can
  // call `await averHandle(0)` instead of poking `instance.exports`
  // directly (its `aver_http_handle` slot is read-only).
  if (
    typeof instance.exports.aver_http_handle === "function" &&
    typeof WebAssembly.promising === "function"
  ) {
    globalThis.__aver_http_handle = WebAssembly.promising(
      instance.exports.aver_http_handle,
    );
  } else if (typeof instance.exports.aver_http_handle === "function") {
    // No JSPI on this host — call sync. Programs that hit Http.*
    // get a transport-error fallback from `makeSuspendingHttpSend`.
    globalThis.__aver_http_handle = instance.exports.aver_http_handle;
  }
  return instance;
}

let instancePromise = null;

export default {
  async fetch(request, env, ctx) {
    if (!instancePromise) instancePromise = instantiate();
    const user = await instancePromise;

    // If --handler was passed, the user's HTTP handler is exported
    // as `aver_http_handle`. We pre-read the body, stash request
    // state, invoke the handler, then build a native Response from
    // whatever the handler stashed via `response_text`.
    if (typeof user.exports.aver_http_handle === "function") {
      const body = await request.text();
      pending = { req: request, body, env, response: null, requestHeaders: [] };
      try {
        // `__aver_http_handle` is the JSPI-wrapped entry — `await`
        // it so any `Http.*` suspension chain resolves before we
        // build the Response. Falls back to a sync call when
        // JSPI isn't available (see `instantiate`).
        await globalThis.__aver_http_handle(0);
      } catch (e) {
        console.error("aver_http_handle threw:", e);
        pending = null;
        return new Response("Internal error\n", { status: 500 });
      }
      const resp = pending.response;
      pending = null;
      if (!resp) {
        return new Response(
          "Handler returned without calling Response builder\n",
          { status: 500 },
        );
      }
      // Build the native Headers from the (name, value) pairs the
      // guest produced. Web Fetch `Headers.append` is multi-value
      // safe — duplicate Set-Cookie entries stay distinct, comma-
      // joined headers are emitted as separate appends so the host
      // can decide whether to fold (per RFC 9110 §5.3 it can; for
      // Set-Cookie per RFC 6265 it shouldn't).
      const headers = new Headers();
      for (const [name, value] of resp.headers ?? []) {
        headers.append(name, value);
      }
      // Default content-type only when the guest didn't set one.
      if (!headers.has("content-type")) {
        headers.set("content-type", "text/plain;charset=utf-8");
      }
      return new Response(resp.body, { status: resp.status, headers });
    }

    // No --handler: run _start once for side-effect output, return
    // a placeholder response. Useful for "Hello from Aver" demos
    // that just want to prove the toolchain works.
    if (typeof user.exports._start === "function") {
      user.exports._start();
    } else if (typeof user.exports.main === "function") {
      user.exports.main();
    }
    return new Response("Aver program ran. Output went to wrangler logs.\n", {
      headers: { "content-type": "text/plain;charset=utf-8" },
    });
  },
};
