"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.serverResponseBodyToReadableStream = serverResponseBodyToReadableStream;
exports.boot = boot;
const eventsource_1 = require("eventsource");
const http = __importStar(require("http"));
const jsdom_1 = require("jsdom");
const web_1 = require("node:stream/web");
const ws_1 = require("ws");
const object_1 = require("../../../util/object");
const readable_1 = require("../../../util/readable");
const SYSTEM_ROOT_ID_1 = require("../../constants/SYSTEM_ROOT_ID");
const intercept_1 = require("../web/api/intercept");
const boot_1 = require("../web/boot");
const canvas_1 = require("./canvas");
function incomingMessageToReadableStream(system, incomingMessage) {
    const { api: { window: { ReadableStream }, }, } = system;
    return new ReadableStream({
        start(controller) {
            incomingMessage.on('data', (chunk) => {
                controller.enqueue(new Uint8Array(chunk));
            });
            incomingMessage.on('end', () => {
                controller.close();
            });
            incomingMessage.on('error', (err) => {
                controller.error(err);
            });
        },
    });
}
function serverResponseBodyToReadableStream(system, body) {
    const { api: { window: { ReadableStream }, }, } = system;
    if (typeof body === 'string') {
        return (0, readable_1.stringToReadableStream)(system, body);
    }
    else if (body instanceof ReadableStream) {
        return body;
    }
    else {
        throw new Error('unsupported body');
    }
}
function boot(opt) {
    const { window } = new jsdom_1.JSDOM(`<div id="${SYSTEM_ROOT_ID_1.SYSTEM_ROOT_ID}"></div>`, {
        url: 'http://localhost/',
    });
    const root = window.document.getElementById(SYSTEM_ROOT_ID_1.SYSTEM_ROOT_ID);
    window.fetch = globalThis.fetch;
    window.HTMLCanvasElement.prototype.getContext = (contextId) => {
        if (contextId === '2d') {
            return new canvas_1.NoopCanvasRenderingContext2D();
        }
        return null;
    };
    // @ts-ignore
    window.WebSocket = ws_1.WebSocket;
    // @ts-ignore
    const [system, unlisten] = (0, boot_1.webBoot)(window, root, opt);
    // @ts-ignore
    system.api.window.ReadableStream = web_1.ReadableStream;
    system.api.text.TextEncoder = TextEncoder;
    system.api.text.TextDecoder = TextDecoder;
    const wss = new ws_1.WebSocketServer({ noServer: true });
    async function handleIncoming(req, socket, head, handler) {
        const headers = (0, object_1.mapObjVK)(req.headers, (value) => {
            return (Array.isArray(value) ? value : [value]).join(', ');
        });
        const { method } = req;
        // @ts-ignore
        const protocol = req.socket.encrypted ? 'https' : 'http';
        const url = `${protocol || 'http'}://${req.headers.host}${req.url}`;
        const { pathname: path, port, hostname, origin, search } = new URL(url);
        const query = (0, intercept_1.searchToQuery)(search);
        const body = incomingMessageToReadableStream(system, req);
        const request = {
            url,
            protocol,
            headers,
            method,
            query,
            path,
            search,
            hostname,
            origin,
            port,
            body,
            _: {
                req,
                head,
                socket,
            },
        };
        const response = await handler(request);
        return response;
    }
    system.api.http.createServer = (opt) => {
        const server = {
            listen: function (port, handler, onerror) {
                const server = http.createServer(async (req, res) => {
                    const response = await handleIncoming(req, undefined, undefined, handler);
                    if (response.status >= 100 && response.status <= 599) {
                        res.writeHead(response.status, '', response.headers);
                        res.flushHeaders();
                    }
                    else {
                        res.writeHead(500);
                        res.end();
                        return;
                    }
                    const reader = serverResponseBodyToReadableStream(system, response.body).getReader();
                    try {
                        while (true) {
                            const { done, value } = await reader.read();
                            if (done) {
                                break;
                            }
                            res.write(value);
                        }
                        res.end();
                    }
                    catch (error) {
                        res.statusCode = 500;
                        res.end('Internal Server Error');
                    }
                });
                server.on('upgrade', async (req, socket, head) => {
                    await handleIncoming(req, socket, head, handler);
                });
                server.on('error', async (err) => {
                    if (err.message.startsWith('listen EADDRINUSE: address already in use')) {
                        onerror('address/port already in use');
                    }
                    else {
                        onerror(err.message);
                    }
                });
                server.listen(port);
                return () => {
                    server.close();
                };
            },
        };
        return server;
    };
    system.api.http.EventSource = eventsource_1.EventSource;
    system.api.http.handleUpgrade = async (request, response) => {
        return new Promise(async (resolve) => {
            const req = request._.req;
            const head = await request._.head;
            const socket = request._.socket;
            wss.handleUpgrade(req, socket, head, (ws) => {
                wss.emit('connection', ws, req);
                // ws.on('error', (err) => {
                //   console.error(`WebSocket error: ${err.message}`)
                // })
                // ws.on('close', (code, reason) => {
                //   console.log(
                //     `WebSocket connection closed. Code: ${code}, Reason: ${reason}`
                //   )
                // })
                const socket = {
                    send: function (data) {
                        ws.send(data);
                    },
                    set onmessage(handler) {
                        ws.onmessage = (event) => {
                            const { data } = event;
                            handler(data);
                        };
                    },
                    set onclose(handler) {
                        ws.onclose = (event) => {
                            const { code, reason } = event;
                            handler(code, reason);
                        };
                    },
                };
                resolve(socket);
            });
        });
    };
    return [system, unlisten];
}
exports.default = null;
//# sourceMappingURL=boot.js.map