import url from "url";
import setupDebug from "next/dist/compiled/debug";
import { getCloneableBody } from "../../body-streams";
import { filterReqHeaders } from "../server-ipc/utils";
import { stringifyQuery } from "../../server-route-utils";
import { toNodeOutgoingHttpHeaders } from "../../web/utils";
import { invokeRequest } from "../server-ipc/invoke-request";
import { getCookieParser, setLazyProp } from "../../api-utils";
import { getHostname } from "../../../shared/lib/get-hostname";
import { getRedirectStatus } from "../../../lib/redirect-status";
import { normalizeRepeatedSlashes } from "../../../shared/lib/utils";
import { relativizeURL } from "../../../shared/lib/router/utils/relativize-url";
import { addPathPrefix } from "../../../shared/lib/router/utils/add-path-prefix";
import { pathHasPrefix } from "../../../shared/lib/router/utils/path-has-prefix";
import { detectDomainLocale } from "../../../shared/lib/i18n/detect-domain-locale";
import { normalizeLocalePath } from "../../../shared/lib/i18n/normalize-locale-path";
import { removePathPrefix } from "../../../shared/lib/router/utils/remove-path-prefix";
import { addRequestMeta, getRequestMeta } from "../../request-meta";
import { compileNonPath, matchHas, prepareDestination } from "../../../shared/lib/router/utils/prepare-destination";
const debug = setupDebug("next:router-server:resolve-routes");
export function getResolveRoutes(fsChecker, config, opts, renderWorkers, renderWorkerOpts, ensureMiddleware) {
    const routes = [
        // _next/data with middleware handling
        {
            match: ()=>({}),
            name: "middleware_next_data"
        },
        ...opts.minimalMode ? [] : fsChecker.headers,
        ...opts.minimalMode ? [] : fsChecker.redirects,
        // check middleware (using matchers)
        {
            match: ()=>({}),
            name: "middleware"
        },
        ...opts.minimalMode ? [] : fsChecker.rewrites.beforeFiles,
        // check middleware (using matchers)
        {
            match: ()=>({}),
            name: "before_files_end"
        },
        // we check exact matches on fs before continuing to
        // after files rewrites
        {
            match: ()=>({}),
            name: "check_fs"
        },
        ...opts.minimalMode ? [] : fsChecker.rewrites.afterFiles,
        // we always do the check: true handling before continuing to
        // fallback rewrites
        {
            check: true,
            match: ()=>({}),
            name: "after files check: true"
        },
        ...opts.minimalMode ? [] : fsChecker.rewrites.fallback
    ];
    async function resolveRoutes(req, matchedDynamicRoutes, isUpgradeReq) {
        var _ref;
        let finished = false;
        let resHeaders = {};
        let matchedOutput = null;
        let parsedUrl = url.parse(req.url || "", true);
        let didRewrite = false;
        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(/(\\|\/\/)/)) {
            parsedUrl = url.parse(normalizeRepeatedSlashes(req.url), true);
            return {
                parsedUrl,
                resHeaders,
                finished: true,
                statusCode: 308
            };
        }
        // TODO: inherit this from higher up
        const protocol = ((_ref = req == null ? void 0 : req.socket) == null ? void 0 : _ref.encrypted) || req.headers["x-forwarded-proto"] === "https" ? "https" : "http";
        // When there are hostname and port we build an absolute URL
        const initUrl = config.experimental.trustHostHeader ? `https://${req.headers.host || "localhost"}${req.url}` : opts.port ? `${protocol}://${opts.hostname || "localhost"}:${opts.port}${req.url}` : req.url || "";
        addRequestMeta(req, "__NEXT_INIT_URL", initUrl);
        addRequestMeta(req, "__NEXT_INIT_QUERY", {
            ...parsedUrl.query
        });
        addRequestMeta(req, "_protocol", protocol);
        setLazyProp({
            req
        }, "cookies", ()=>getCookieParser(req.headers)());
        if (!isUpgradeReq) {
            addRequestMeta(req, "__NEXT_CLONABLE_BODY", getCloneableBody(req));
        }
        const maybeAddTrailingSlash = (pathname)=>{
            if (config.trailingSlash && !config.skipMiddlewareUrlNormalize && !pathname.endsWith("/")) {
                return `${pathname}/`;
            }
            return pathname;
        };
        let domainLocale;
        let defaultLocale;
        let initialLocaleResult = undefined;
        if (config.i18n) {
            var _parsedUrl_pathname;
            const hadTrailingSlash = (_parsedUrl_pathname = parsedUrl.pathname) == null ? void 0 : _parsedUrl_pathname.endsWith("/");
            const hadBasePath = pathHasPrefix(parsedUrl.pathname || "", config.basePath);
            initialLocaleResult = normalizeLocalePath(removePathPrefix(parsedUrl.pathname || "/", config.basePath), config.i18n.locales);
            domainLocale = detectDomainLocale(config.i18n.domains, getHostname(parsedUrl, req.headers));
            defaultLocale = (domainLocale == null ? void 0 : domainLocale.defaultLocale) || config.i18n.defaultLocale;
            parsedUrl.query.__nextDefaultLocale = defaultLocale;
            parsedUrl.query.__nextLocale = initialLocaleResult.detectedLocale || defaultLocale;
            // ensure locale is present for resolving routes
            if (!initialLocaleResult.detectedLocale && !initialLocaleResult.pathname.startsWith("/_next/")) {
                parsedUrl.pathname = addPathPrefix(initialLocaleResult.pathname === "/" ? `/${defaultLocale}` : addPathPrefix(initialLocaleResult.pathname || "", `/${defaultLocale}`), hadBasePath ? config.basePath : "");
                if (hadTrailingSlash) {
                    parsedUrl.pathname = maybeAddTrailingSlash(parsedUrl.pathname);
                }
            }
        }
        const checkLocaleApi = (pathname)=>{
            if (config.i18n && pathname === urlNoQuery && (initialLocaleResult == null ? void 0 : initialLocaleResult.detectedLocale) && pathHasPrefix(initialLocaleResult.pathname, "/api")) {
                return true;
            }
        };
        async function checkTrue() {
            if (checkLocaleApi(parsedUrl.pathname || "")) {
                return;
            }
            const output = await fsChecker.getItem(parsedUrl.pathname || "");
            if (output) {
                if (config.useFileSystemPublicRoutes || didRewrite || output.type !== "appFile" && output.type !== "pageFile") {
                    return output;
                }
            }
            const dynamicRoutes = fsChecker.getDynamicRoutes();
            let curPathname = parsedUrl.pathname;
            if (config.basePath) {
                if (!pathHasPrefix(curPathname || "", config.basePath)) {
                    return;
                }
                curPathname = (curPathname == null ? void 0 : curPathname.substring(config.basePath.length)) || "/";
            }
            const localeResult = fsChecker.handleLocale(curPathname || "");
            for (const route of dynamicRoutes){
                // when resolving fallback: false we attempt to
                // render worker may return a no-fallback response
                // which signals we need to continue resolving.
                // TODO: optimize this to collect static paths
                // to use at the routing layer
                if (matchedDynamicRoutes.has(route.page)) {
                    continue;
                }
                const params = route.match(localeResult.pathname);
                if (params) {
                    const pageOutput = await fsChecker.getItem(addPathPrefix(route.page, config.basePath || ""));
                    // i18n locales aren't matched for app dir
                    if ((pageOutput == null ? void 0 : pageOutput.type) === "appFile" && (initialLocaleResult == null ? void 0 : initialLocaleResult.detectedLocale)) {
                        continue;
                    }
                    if (pageOutput && (curPathname == null ? void 0 : curPathname.startsWith("/_next/data"))) {
                        parsedUrl.query.__nextDataReq = "1";
                    }
                    matchedDynamicRoutes.add(route.page);
                    if (config.useFileSystemPublicRoutes || didRewrite) {
                        return pageOutput;
                    }
                }
            }
        }
        async function handleRoute(route) {
            let curPathname = parsedUrl.pathname || "/";
            if (config.i18n && route.internal) {
                const hadTrailingSlash = curPathname.endsWith("/");
                if (config.basePath) {
                    curPathname = removePathPrefix(curPathname, config.basePath);
                }
                const hadBasePath = curPathname !== parsedUrl.pathname;
                const localeResult = normalizeLocalePath(curPathname, config.i18n.locales);
                const isDefaultLocale = localeResult.detectedLocale === defaultLocale;
                if (isDefaultLocale) {
                    curPathname = localeResult.pathname === "/" && hadBasePath ? config.basePath : addPathPrefix(localeResult.pathname, hadBasePath ? config.basePath : "");
                } else if (hadBasePath) {
                    curPathname = curPathname === "/" ? config.basePath : addPathPrefix(curPathname, config.basePath);
                }
                if ((isDefaultLocale || hadBasePath) && hadTrailingSlash) {
                    curPathname = maybeAddTrailingSlash(curPathname);
                }
            }
            let params = route.match(curPathname);
            if ((route.has || route.missing) && params) {
                const hasParams = matchHas(req, parsedUrl.query, route.has, route.missing);
                if (hasParams) {
                    Object.assign(params, hasParams);
                } else {
                    params = false;
                }
            }
            if (params) {
                if (fsChecker.interceptionRoutes && route.name === "before_files_end") {
                    for (const interceptionRoute of fsChecker.interceptionRoutes){
                        const result = await handleRoute(interceptionRoute);
                        if (result) {
                            return result;
                        }
                    }
                }
                if (route.name === "middleware_next_data") {
                    var _fsChecker_getMiddlewareMatchers;
                    if ((_fsChecker_getMiddlewareMatchers = fsChecker.getMiddlewareMatchers()) == null ? void 0 : _fsChecker_getMiddlewareMatchers.length) {
                        var _parsedUrl_pathname;
                        const nextDataPrefix = addPathPrefix(`/_next/data/${fsChecker.buildId}/`, config.basePath);
                        if (((_parsedUrl_pathname = parsedUrl.pathname) == null ? void 0 : _parsedUrl_pathname.startsWith(nextDataPrefix)) && parsedUrl.pathname.endsWith(".json")) {
                            parsedUrl.query.__nextDataReq = "1";
                            parsedUrl.pathname = parsedUrl.pathname.substring(nextDataPrefix.length - 1);
                            parsedUrl.pathname = parsedUrl.pathname.substring(0, parsedUrl.pathname.length - ".json".length);
                            parsedUrl.pathname = addPathPrefix(parsedUrl.pathname || "", config.basePath);
                            parsedUrl.pathname = parsedUrl.pathname === "/index" ? "/" : parsedUrl.pathname;
                            parsedUrl.pathname = maybeAddTrailingSlash(parsedUrl.pathname);
                        }
                    }
                }
                if (route.name === "check_fs") {
                    if (checkLocaleApi(parsedUrl.pathname || "")) {
                        return;
                    }
                    const output = await fsChecker.getItem(parsedUrl.pathname || "");
                    if (output && !(config.i18n && (initialLocaleResult == null ? void 0 : initialLocaleResult.detectedLocale) && pathHasPrefix(parsedUrl.pathname || "", "/api"))) {
                        if (config.useFileSystemPublicRoutes || didRewrite || output.type !== "appFile" && output.type !== "pageFile") {
                            matchedOutput = output;
                            if (output.locale) {
                                parsedUrl.query.__nextLocale = output.locale;
                            }
                            return {
                                parsedUrl,
                                resHeaders,
                                finished: true,
                                matchedOutput
                            };
                        }
                    }
                }
                if (!opts.minimalMode && route.name === "middleware") {
                    const match = fsChecker.getMiddlewareMatchers();
                    if (// @ts-expect-error BaseNextRequest stuff
                    (match == null ? void 0 : match(parsedUrl.pathname, req, parsedUrl.query)) && (!ensureMiddleware || await (ensureMiddleware == null ? void 0 : ensureMiddleware().then(()=>true).catch(()=>false)))) {
                        var _ref, _getRequestMeta;
                        const workerResult = await ((_ref = renderWorkers.app || renderWorkers.pages) == null ? void 0 : _ref.initialize(renderWorkerOpts));
                        if (!workerResult) {
                            throw new Error(`Failed to initialize render worker "middleware"`);
                        }
                        const stringifiedQuery = stringifyQuery(req, getRequestMeta(req, "__NEXT_INIT_QUERY") || {});
                        const parsedInitUrl = new URL(getRequestMeta(req, "__NEXT_INIT_URL") || "/", "http://n");
                        const curUrl = config.skipMiddlewareUrlNormalize ? `${parsedInitUrl.pathname}${parsedInitUrl.search}` : `${parsedUrl.pathname}${stringifiedQuery ? "?" : ""}${stringifiedQuery || ""}`;
                        const renderUrl = `http://${workerResult.hostname}:${workerResult.port}${curUrl}`;
                        const invokeHeaders = {
                            ...req.headers,
                            "x-invoke-path": "",
                            "x-invoke-query": "",
                            "x-invoke-output": "",
                            "x-middleware-invoke": "1"
                        };
                        debug("invoking middleware", renderUrl, invokeHeaders);
                        const middlewareRes = await invokeRequest(renderUrl, {
                            headers: invokeHeaders,
                            method: req.method
                        }, (_getRequestMeta = getRequestMeta(req, "__NEXT_CLONABLE_BODY")) == null ? void 0 : _getRequestMeta.cloneBodyStream());
                        const middlewareHeaders = toNodeOutgoingHttpHeaders(middlewareRes.headers);
                        debug("middleware res", middlewareRes.status, middlewareHeaders);
                        if (middlewareHeaders["x-middleware-override-headers"]) {
                            const overriddenHeaders = new Set();
                            let overrideHeaders = middlewareHeaders["x-middleware-override-headers"];
                            if (typeof overrideHeaders === "string") {
                                overrideHeaders = overrideHeaders.split(",");
                            }
                            for (const key of overrideHeaders){
                                overriddenHeaders.add(key.trim());
                            }
                            delete middlewareHeaders["x-middleware-override-headers"];
                            // Delete headers.
                            for (const key of Object.keys(req.headers)){
                                if (!overriddenHeaders.has(key)) {
                                    delete req.headers[key];
                                }
                            }
                            // Update or add headers.
                            for (const key of overriddenHeaders.keys()){
                                const valueKey = "x-middleware-request-" + key;
                                const newValue = middlewareHeaders[valueKey];
                                const oldValue = req.headers[key];
                                if (oldValue !== newValue) {
                                    req.headers[key] = newValue === null ? undefined : newValue;
                                }
                                delete middlewareHeaders[valueKey];
                            }
                        }
                        if (!middlewareHeaders["x-middleware-rewrite"] && !middlewareHeaders["x-middleware-next"] && !middlewareHeaders["location"]) {
                            middlewareHeaders["x-middleware-refresh"] = "1";
                        }
                        delete middlewareHeaders["x-middleware-next"];
                        for (const [key, value] of Object.entries({
                            ...filterReqHeaders(middlewareHeaders)
                        })){
                            if ([
                                "content-length",
                                "x-middleware-rewrite",
                                "x-middleware-redirect",
                                "x-middleware-refresh",
                                "x-middleware-invoke",
                                "x-invoke-path",
                                "x-invoke-query"
                            ].includes(key)) {
                                continue;
                            }
                            if (value) {
                                resHeaders[key] = value;
                                req.headers[key] = value;
                            }
                        }
                        if (middlewareHeaders["x-middleware-rewrite"]) {
                            const value = middlewareHeaders["x-middleware-rewrite"];
                            const rel = relativizeURL(value, initUrl);
                            resHeaders["x-middleware-rewrite"] = rel;
                            const query = parsedUrl.query;
                            parsedUrl = url.parse(rel, true);
                            if (parsedUrl.protocol) {
                                return {
                                    parsedUrl,
                                    resHeaders,
                                    finished: true
                                };
                            }
                            // keep internal query state
                            for (const key of Object.keys(query)){
                                if (key.startsWith("_next") || key.startsWith("__next")) {
                                    parsedUrl.query[key] = query[key];
                                }
                            }
                            if (config.i18n) {
                                const curLocaleResult = normalizeLocalePath(parsedUrl.pathname || "", config.i18n.locales);
                                if (curLocaleResult.detectedLocale) {
                                    parsedUrl.query.__nextLocale = curLocaleResult.detectedLocale;
                                }
                            }
                        }
                        if (middlewareHeaders["location"]) {
                            const value = middlewareHeaders["location"];
                            const rel = relativizeURL(value, initUrl);
                            resHeaders["location"] = rel;
                            parsedUrl = url.parse(rel, true);
                            return {
                                parsedUrl,
                                resHeaders,
                                finished: true,
                                statusCode: middlewareRes.status
                            };
                        }
                        if (middlewareHeaders["x-middleware-refresh"]) {
                            return {
                                parsedUrl,
                                resHeaders,
                                finished: true,
                                bodyStream: middlewareRes.body,
                                statusCode: middlewareRes.status
                            };
                        }
                    }
                }
                // handle redirect
                if (("statusCode" in route || "permanent" in route) && route.destination) {
                    const { parsedDestination  } = prepareDestination({
                        appendParamsToQuery: false,
                        destination: route.destination,
                        params: params,
                        query: parsedUrl.query
                    });
                    const { query  } = parsedDestination;
                    delete parsedDestination.query;
                    parsedDestination.search = stringifyQuery(req, query);
                    parsedDestination.pathname = normalizeRepeatedSlashes(parsedDestination.pathname);
                    return {
                        finished: true,
                        // @ts-expect-error custom ParsedUrl
                        parsedUrl: parsedDestination,
                        statusCode: getRedirectStatus(route)
                    };
                }
                // handle headers
                if (route.headers) {
                    const hasParams = Object.keys(params).length > 0;
                    for (const header of route.headers){
                        let { key , value  } = header;
                        if (hasParams) {
                            key = compileNonPath(key, params);
                            value = compileNonPath(value, params);
                        }
                        if (key.toLowerCase() === "set-cookie") {
                            if (!Array.isArray(resHeaders[key])) {
                                const val = resHeaders[key];
                                resHeaders[key] = typeof val === "string" ? [
                                    val
                                ] : [];
                            }
                            resHeaders[key].push(value);
                        } else {
                            resHeaders[key] = value;
                        }
                    }
                }
                // handle rewrite
                if (route.destination) {
                    const { parsedDestination  } = prepareDestination({
                        appendParamsToQuery: true,
                        destination: route.destination,
                        params: params,
                        query: parsedUrl.query
                    });
                    if (parsedDestination.protocol) {
                        return {
                            // @ts-expect-error custom ParsedUrl
                            parsedUrl: parsedDestination,
                            finished: true
                        };
                    }
                    if (config.i18n) {
                        const curLocaleResult = normalizeLocalePath(removePathPrefix(parsedDestination.pathname, config.basePath), config.i18n.locales);
                        if (curLocaleResult.detectedLocale) {
                            parsedUrl.query.__nextLocale = curLocaleResult.detectedLocale;
                        }
                    }
                    didRewrite = true;
                    parsedUrl.pathname = parsedDestination.pathname;
                    Object.assign(parsedUrl.query, parsedDestination.query);
                }
                // handle check: true
                if (route.check) {
                    const output = await checkTrue();
                    if (output) {
                        return {
                            parsedUrl,
                            resHeaders,
                            finished: true,
                            matchedOutput: output
                        };
                    }
                }
            }
        }
        for (const route of routes){
            const result = await handleRoute(route);
            if (result) {
                return result;
            }
        }
        return {
            finished,
            parsedUrl,
            resHeaders,
            matchedOutput
        };
    }
    return resolveRoutes;
}

//# sourceMappingURL=resolve-routes.js.map