///|
/// Drain queued script channel callback payloads as serialized BiDi values.
extern "js" fn js_take_channel_messages() -> String =
#| () => {
#| const queue = Array.isArray(globalThis.__bidiChannelMessages)
#| ? globalThis.__bidiChannelMessages
#| : [];
#| globalThis.__bidiChannelMessages = [];
#| if (typeof globalThis.__bidiNextChannelHandleId !== "number") {
#| globalThis.__bidiNextChannelHandleId = 1;
#| }
#|
#| const serialize = (value, maxObjectDepth, depth = 0) => {
#| if (value === undefined) return { type: "undefined" };
#| if (value === null) return { type: "null" };
#| const type = typeof value;
#| if (type === "boolean") return { type: "boolean", value };
#| if (type === "number") {
#| if (Number.isNaN(value)) return { type: "number", value: "NaN" };
#| if (!Number.isFinite(value)) return { type: "number", value: value > 0 ? "Infinity" : "-Infinity" };
#| if (Object.is(value, -0)) return { type: "number", value: "-0" };
#| return { type: "number", value };
#| }
#| if (type === "string") return { type: "string", value };
#| if (type === "bigint") return { type: "bigint", value: String(value) };
#| if (type === "symbol") return { type: "symbol" };
#| if (type === "function") return { type: "function" };
#|
#| const limitReached =
#| typeof maxObjectDepth === "number" &&
#| maxObjectDepth >= 0 &&
#| depth >= maxObjectDepth;
#|
#| if (Array.isArray(value)) {
#| if (limitReached) return { type: "array" };
#| return { type: "array", value: value.map((v) => serialize(v, maxObjectDepth, depth + 1)) };
#| }
#| if (value instanceof Map) {
#| if (limitReached) return { type: "map" };
#| const entries = [];
#| for (const [k, v] of value) {
#| entries.push([typeof k === "string" ? k : serialize(k, maxObjectDepth, depth + 1), serialize(v, maxObjectDepth, depth + 1)]);
#| }
#| return { type: "map", value: entries };
#| }
#| if (value instanceof Set) {
#| if (limitReached) return { type: "set" };
#| return { type: "set", value: Array.from(value).map((v) => serialize(v, maxObjectDepth, depth + 1)) };
#| }
#| if (value instanceof RegExp) return { type: "regexp", value: { pattern: value.source, flags: value.flags } };
#| if (value instanceof Date) return { type: "date", value: value.toISOString() };
#|
#| if (value && type === "object" && typeof value.nodeType === "number") {
#| return { type: "node" };
#| }
#| if (value && value.constructor === Object) {
#| if (limitReached) return { type: "object" };
#| const entries = Object.entries(value).map(([k, v]) => [k, serialize(v, maxObjectDepth, depth + 1)]);
#| return { type: "object", value: entries };
#| }
#| return { type: "object" };
#| };
#|
#| const events = queue.map((entry) => {
#| const channel = entry && typeof entry.channel === "string" ? entry.channel : "";
#| const ownership = entry && entry.ownership === "root" ? "root" : "none";
#| const maxObjectDepth =
#| entry && typeof entry.maxObjectDepth === "number"
#| ? entry.maxObjectDepth
#| : -1;
#| let data = serialize(entry ? entry.data : undefined, maxObjectDepth, 0);
#| if (ownership === "root" && data && typeof data === "object") {
#| data = {
#| ...data,
#| handle: `channel-handle-${globalThis.__bidiNextChannelHandleId++}`,
#| };
#| }
#| return { channel, data };
#| }).filter((entry) => !!entry.channel);
#|
#| return JSON.stringify(events);
#| }
///|
/// Drain queued script channel callback payloads as JSON.
pub fn take_channel_messages_json() -> String {
js_take_channel_messages()
}