import "../node-polyfill-fetch";
import http from "http";
import { isIPv6 } from "net";
import { initialEnv } from "@next/env";
import * as Log from "../../build/output/log";
import setupDebug from "next/dist/compiled/debug";
import { splitCookiesString, toNodeOutgoingHttpHeaders } from "../web/utils";
import { getCloneableBody } from "../body-streams";
import { filterReqHeaders } from "./server-ipc/utils";
import setupCompression from "next/dist/compiled/compression";
import { normalizeRepeatedSlashes } from "../../shared/lib/utils";
import { invokeRequest } from "./server-ipc/invoke-request";
import { isAbortError, pipeReadable } from "../pipe-readable";
import { genRouterWorkerExecArgv, getDebugPort, getNodeOptionsWithoutInspect } from "./utils";
import { signalFromNodeResponse } from "../web/spec-extension/adapters/next-request";
const debug = setupDebug("next:start-server");
export const checkIsNodeDebugging = ()=>{
    var _process_env_NODE_OPTIONS, _process_env_NODE_OPTIONS1;
    let isNodeDebugging = !!(process.execArgv.some((localArg)=>localArg.startsWith("--inspect")) || ((_process_env_NODE_OPTIONS = process.env.NODE_OPTIONS) == null ? void 0 : _process_env_NODE_OPTIONS.match == null ? void 0 : _process_env_NODE_OPTIONS.match(/--inspect(=\S+)?( |$)/)));
    if (process.execArgv.some((localArg)=>localArg.startsWith("--inspect-brk")) || ((_process_env_NODE_OPTIONS1 = process.env.NODE_OPTIONS) == null ? void 0 : _process_env_NODE_OPTIONS1.match == null ? void 0 : _process_env_NODE_OPTIONS1.match(/--inspect-brk(=\S+)?( |$)/))) {
        isNodeDebugging = "brk";
    }
    return isNodeDebugging;
};
export const createRouterWorker = async (routerServerPath, isNodeDebugging, jestWorkerPath = require.resolve("next/dist/compiled/jest-worker"))=>{
    const { Worker  } = require(jestWorkerPath);
    return new Worker(routerServerPath, {
        numWorkers: 1,
        // TODO: do we want to allow more than 8 OOM restarts?
        maxRetries: 8,
        forkOptions: {
            execArgv: await genRouterWorkerExecArgv(isNodeDebugging === undefined ? false : isNodeDebugging),
            env: {
                FORCE_COLOR: "1",
                ...initialEnv || process.env,
                NODE_OPTIONS: getNodeOptionsWithoutInspect(),
                ...process.env.NEXT_CPU_PROF ? {
                    __NEXT_PRIVATE_CPU_PROFILE: `CPU.router`
                } : {},
                WATCHPACK_WATCHER_LIMIT: "20",
                EXPERIMENTAL_TURBOPACK: process.env.EXPERIMENTAL_TURBOPACK
            }
        },
        exposedMethods: [
            "initialize"
        ]
    });
};
export async function startServer({ dir , nextConfig , prevDir , port , isDev , hostname , useWorkers , minimalMode , allowRetry , keepAliveTimeout , onStdout , onStderr , logReady =true  }) {
    const sockets = new Set();
    let worker;
    let routerPort;
    let handlersReady = ()=>{};
    let handlersError = ()=>{};
    let handlersPromise = new Promise((resolve, reject)=>{
        handlersReady = resolve;
        handlersError = reject;
    });
    let requestHandler = async (_req, _res)=>{
        if (handlersPromise) {
            await handlersPromise;
            return requestHandler(_req, _res);
        }
        throw new Error("Invariant request handler was not setup");
    };
    let upgradeHandler = async (_req, _socket, _head)=>{
        if (handlersPromise) {
            await handlersPromise;
            return upgradeHandler(_req, _socket, _head);
        }
        throw new Error("Invariant upgrade handler was not setup");
    };
    // setup server listener as fast as possible
    const server = http.createServer(async (req, res)=>{
        try {
            if (handlersPromise) {
                await handlersPromise;
                handlersPromise = undefined;
            }
            sockets.add(res);
            res.on("close", ()=>sockets.delete(res));
            await requestHandler(req, res);
        } catch (err) {
            res.statusCode = 500;
            res.end("Internal Server Error");
            Log.error(`Failed to handle request for ${req.url}`);
            console.error(err);
        }
    });
    if (keepAliveTimeout) {
        server.keepAliveTimeout = keepAliveTimeout;
    }
    server.on("upgrade", async (req, socket, head)=>{
        try {
            sockets.add(socket);
            socket.on("close", ()=>sockets.delete(socket));
            await upgradeHandler(req, socket, head);
        } catch (err) {
            socket.destroy();
            Log.error(`Failed to handle request for ${req.url}`);
            console.error(err);
        }
    });
    let portRetryCount = 0;
    server.on("error", (err)=>{
        if (allowRetry && port && isDev && err.code === "EADDRINUSE" && portRetryCount < 10) {
            Log.warn(`Port ${port} is in use, trying ${port + 1} instead.`);
            port += 1;
            portRetryCount += 1;
            server.listen(port, hostname);
        } else {
            Log.error(`Failed to start server`);
            console.error(err);
            process.exit(1);
        }
    });
    let targetHost = hostname;
    const isNodeDebugging = checkIsNodeDebugging();
    await new Promise((resolve)=>{
        server.on("listening", ()=>{
            const addr = server.address();
            port = typeof addr === "object" ? (addr == null ? void 0 : addr.port) || port : port;
            let host = !hostname || hostname === "0.0.0.0" ? "localhost" : hostname;
            let normalizedHostname = hostname || "0.0.0.0";
            if (isIPv6(hostname)) {
                host = host === "::" ? "[::1]" : `[${host}]`;
                normalizedHostname = `[${hostname}]`;
            }
            targetHost = host;
            const appUrl = `http://${host}:${port}`;
            if (isNodeDebugging) {
                const debugPort = getDebugPort();
                Log.info(`the --inspect${isNodeDebugging === "brk" ? "-brk" : ""} option was detected, the Next.js proxy server should be inspected at port ${debugPort}.`);
            }
            if (logReady) {
                Log.ready(`started server on ${normalizedHostname}${(port + "").startsWith(":") ? "" : ":"}${port}, url: ${appUrl}`);
                // expose the main port to render workers
                process.env.PORT = port + "";
            }
            resolve();
        });
        server.listen(port, hostname === "localhost" ? "0.0.0.0" : hostname);
    });
    try {
        if (useWorkers) {
            var _routerWorker__workerPool;
            const httpProxy = require("next/dist/compiled/http-proxy");
            let routerServerPath = require.resolve("./router-server");
            let jestWorkerPath = require.resolve("next/dist/compiled/jest-worker");
            if (prevDir) {
                jestWorkerPath = jestWorkerPath.replace(prevDir, dir);
                routerServerPath = routerServerPath.replace(prevDir, dir);
            }
            const routerWorker = await createRouterWorker(routerServerPath, isNodeDebugging, jestWorkerPath);
            const cleanup = ()=>{
                var _routerWorker__workerPool;
                debug("start-server process cleanup");
                for (const curWorker of ((_routerWorker__workerPool = routerWorker._workerPool) == null ? void 0 : _routerWorker__workerPool._workers) || []){
                    var _curWorker__child;
                    (_curWorker__child = curWorker._child) == null ? void 0 : _curWorker__child.kill("SIGKILL");
                }
            };
            process.on("exit", cleanup);
            process.on("SIGINT", cleanup);
            process.on("SIGTERM", cleanup);
            process.on("uncaughtException", cleanup);
            process.on("unhandledRejection", cleanup);
            let didInitialize = false;
            for (const _worker of ((_routerWorker__workerPool = routerWorker._workerPool) == null ? void 0 : _routerWorker__workerPool._workers) || []){
                // eslint-disable-next-line no-loop-func
                _worker._child.on("exit", (code, signal)=>{
                    // catch failed initializing without retry
                    if ((code || signal) && !didInitialize) {
                        routerWorker == null ? void 0 : routerWorker.end();
                        process.exit(1);
                    }
                });
            }
            const workerStdout = routerWorker.getStdout();
            const workerStderr = routerWorker.getStderr();
            workerStdout.on("data", (data)=>{
                if (typeof onStdout === "function") {
                    onStdout(data);
                } else {
                    process.stdout.write(data);
                }
            });
            workerStderr.on("data", (data)=>{
                if (typeof onStderr === "function") {
                    onStderr(data);
                } else {
                    process.stderr.write(data);
                }
            });
            const initializeResult = await routerWorker.initialize({
                dir,
                port,
                hostname,
                dev: !!isDev,
                minimalMode,
                workerType: "router",
                isNodeDebugging: !!isNodeDebugging,
                keepAliveTimeout
            });
            routerPort = initializeResult.port;
            didInitialize = true;
            let compress;
            if ((nextConfig == null ? void 0 : nextConfig.compress) !== false) {
                compress = setupCompression();
            }
            const getProxyServer = (pathname)=>{
                const targetUrl = `http://${targetHost === "localhost" ? "127.0.0.1" : targetHost}:${routerPort}${pathname}`;
                const proxyServer = httpProxy.createProxy({
                    target: targetUrl,
                    changeOrigin: false,
                    ignorePath: true,
                    xfwd: true,
                    ws: true,
                    followRedirects: false
                });
                // add error listener to prevent uncaught exceptions
                proxyServer.on("error", (_err)=>{
                // TODO?: enable verbose error logs with --debug flag?
                });
                proxyServer.on("proxyRes", (proxyRes, innerReq, innerRes)=>{
                    const cleanupProxy = (err)=>{
                        // cleanup event listeners to allow clean garbage collection
                        proxyRes.removeListener("error", cleanupProxy);
                        proxyRes.removeListener("close", cleanupProxy);
                        innerRes.removeListener("error", cleanupProxy);
                        innerRes.removeListener("close", cleanupProxy);
                        // destroy all source streams to propagate the caught event backward
                        innerReq.destroy(err);
                        proxyRes.destroy(err);
                    };
                    proxyRes.once("error", cleanupProxy);
                    proxyRes.once("close", cleanupProxy);
                    innerRes.once("error", cleanupProxy);
                    innerRes.once("close", cleanupProxy);
                });
                return proxyServer;
            };
            // proxy to router worker
            requestHandler = async (req, res)=>{
                const urlParts = (req.url || "").split("?");
                const urlNoQuery = urlParts[0];
                // this normalizes repeated slashes in the path e.g. hello//world ->
                // hello/world or backslashes to forward slashes, this does not
                // handle trailing slash as that is handled the same as a next.config.js
                // redirect
                if (urlNoQuery == null ? void 0 : urlNoQuery.match(/(\\|\/\/)/)) {
                    const cleanUrl = normalizeRepeatedSlashes(req.url);
                    res.statusCode = 308;
                    res.setHeader("Location", cleanUrl);
                    res.end(cleanUrl);
                    return;
                }
                if (typeof compress === "function") {
                    // @ts-expect-error not express req/res
                    compress(req, res, ()=>{});
                }
                const targetUrl = `http://${targetHost === "localhost" ? "127.0.0.1" : targetHost}:${routerPort}${req.url || "/"}`;
                let invokeRes;
                try {
                    invokeRes = await invokeRequest(targetUrl, {
                        headers: req.headers,
                        method: req.method,
                        signal: signalFromNodeResponse(res)
                    }, getCloneableBody(req).cloneBodyStream());
                } catch (e) {
                    // If the client aborts before we can receive a response object (when
                    // the headers are flushed), then we can early exit without further
                    // processing.
                    if (isAbortError(e)) {
                        return;
                    }
                    throw e;
                }
                res.statusCode = invokeRes.status;
                res.statusMessage = invokeRes.statusText;
                for (const [key, value] of Object.entries(filterReqHeaders(toNodeOutgoingHttpHeaders(invokeRes.headers)))){
                    if (value !== undefined) {
                        if (key === "set-cookie") {
                            const curValue = res.getHeader(key);
                            const newValue = [];
                            for (const cookie of Array.isArray(curValue) ? curValue : splitCookiesString(curValue || "")){
                                newValue.push(cookie);
                            }
                            for (const val of Array.isArray(value) ? value : value ? [
                                value
                            ] : []){
                                newValue.push(val);
                            }
                            res.setHeader(key, newValue);
                        } else {
                            res.setHeader(key, value);
                        }
                    }
                }
                if (invokeRes.body) {
                    await pipeReadable(invokeRes.body, res);
                } else {
                    res.end();
                }
            };
            upgradeHandler = async (req, socket, head)=>{
                // add error listeners to prevent uncaught exceptions on socket errors
                req.on("error", (_err)=>{
                // TODO: log socket errors?
                // console.log(_err)
                });
                socket.on("error", (_err)=>{
                // TODO: log socket errors?
                // console.log(_err)
                });
                const proxyServer = getProxyServer(req.url || "/");
                proxyServer.on("proxyReqWs", (proxyReq)=>{
                    socket.on("close", ()=>proxyReq.destroy());
                });
                proxyServer.ws(req, socket, head);
            };
            handlersReady();
        } else {
            // when not using a worker start next in main process
            const next = require("../next");
            const addr = server.address();
            const app = next({
                dir,
                hostname,
                dev: isDev,
                isNodeDebugging,
                httpServer: server,
                customServer: false,
                port: addr && typeof addr === "object" ? addr.port : port
            });
            // handle in process
            requestHandler = app.getRequestHandler();
            upgradeHandler = app.getUpgradeHandler();
            await app.prepare();
            handlersReady();
        }
    } catch (err) {
        // fatal error if we can't setup
        handlersError();
        console.error(err);
        process.exit(1);
    }
    // return teardown function for destroying the server
    async function teardown() {
        server.close();
        sockets.forEach((socket)=>{
            sockets.delete(socket);
            socket.destroy();
        });
        if (worker) {
            await worker.end();
        }
    }
    teardown.port = routerPort;
    return teardown;
}

//# sourceMappingURL=start-server.js.map