import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path';
import { getPathMatch } from '../shared/lib/router/utils/path-match';
import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex';
import { getRouteMatcher } from '../shared/lib/router/utils/route-matcher';
import { matchHas, prepareDestination } from '../shared/lib/router/utils/prepare-destination';
import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash';
import { normalizeRscURL } from '../shared/lib/router/utils/app-paths';
import { NEXT_CACHE_REVALIDATE_TAG_TOKEN_HEADER, NEXT_CACHE_REVALIDATED_TAGS_HEADER, NEXT_INTERCEPTION_MARKER_PREFIX, NEXT_QUERY_PARAM_PREFIX } from '../lib/constants';
import { normalizeNextQueryParam } from './web/utils';
import { decodeQueryPathParameter } from './lib/decode-query-path-parameter';
import { parseReqUrl } from '../lib/url';
import { formatUrl } from '../shared/lib/router/utils/format-url';
import { parseAndValidateFlightRouterState } from './app-render/parse-and-validate-flight-router-state';
import { isInterceptionRouteRewrite } from '../lib/generate-interception-routes-rewrites';
import { NEXT_ROUTER_STATE_TREE_HEADER } from '../client/components/app-router-headers';
import { getSelectedParams } from '../client/components/router-reducer/compute-changed-path';
function filterInternalQuery(query, paramKeys) {
    // this is used to pass query information in rewrites
    // but should not be exposed in final query
    delete query['nextInternalLocale'];
    for(const key in query){
        const isNextQueryPrefix = key !== NEXT_QUERY_PARAM_PREFIX && key.startsWith(NEXT_QUERY_PARAM_PREFIX);
        const isNextInterceptionMarkerPrefix = key !== NEXT_INTERCEPTION_MARKER_PREFIX && key.startsWith(NEXT_INTERCEPTION_MARKER_PREFIX);
        if (isNextQueryPrefix || isNextInterceptionMarkerPrefix || paramKeys.includes(key)) {
            delete query[key];
        }
    }
}
export function normalizeCdnUrl(req, paramKeys) {
    // make sure to normalize req.url from CDNs to strip dynamic and rewrite
    // params from the query which are added during routing
    const _parsedUrl = parseReqUrl(req.url);
    // we can't normalize if we can't parse
    if (!_parsedUrl) {
        return req.url;
    }
    delete _parsedUrl.search;
    filterInternalQuery(_parsedUrl.query, paramKeys);
    req.url = formatUrl(_parsedUrl);
}
export function interpolateDynamicPath(pathname, params, defaultRouteRegex) {
    if (!defaultRouteRegex) return pathname;
    for (const param of Object.keys(defaultRouteRegex.groups)){
        const { optional, repeat } = defaultRouteRegex.groups[param];
        let builtParam = `[${repeat ? '...' : ''}${param}]`;
        if (optional) {
            builtParam = `[${builtParam}]`;
        }
        let paramValue;
        const value = params[param];
        if (Array.isArray(value)) {
            paramValue = value.map((v)=>v && encodeURIComponent(v)).join('/');
        } else if (value) {
            paramValue = encodeURIComponent(value);
        } else {
            paramValue = '';
        }
        if (paramValue || optional) {
            pathname = pathname.replaceAll(builtParam, paramValue);
        }
    }
    return pathname;
}
export function normalizeDynamicRouteParams(query, defaultRouteRegex, defaultRouteMatches, ignoreMissingOptional) {
    let hasValidParams = true;
    let params = {};
    for (const key of Object.keys(defaultRouteRegex.groups)){
        let value = query[key];
        if (typeof value === 'string') {
            value = normalizeRscURL(value);
        } else if (Array.isArray(value)) {
            value = value.map(normalizeRscURL);
        }
        // if the value matches the default value we can't rely
        // on the parsed params, this is used to signal if we need
        // to parse x-now-route-matches or not
        const defaultValue = defaultRouteMatches[key];
        const isOptional = defaultRouteRegex.groups[key].optional;
        const isDefaultValue = Array.isArray(defaultValue) ? defaultValue.some((defaultVal)=>{
            return Array.isArray(value) ? value.some((val)=>val.includes(defaultVal)) : value == null ? void 0 : value.includes(defaultVal);
        }) : value == null ? void 0 : value.includes(defaultValue);
        if (isDefaultValue || typeof value === 'undefined' && !(isOptional && ignoreMissingOptional)) {
            return {
                params: {},
                hasValidParams: false
            };
        }
        // non-provided optional values should be undefined so normalize
        // them to undefined
        if (isOptional && (!value || Array.isArray(value) && value.length === 1 && // fallback optional catch-all SSG pages have
        // [[...paramName]] for the root path on Vercel
        (value[0] === 'index' || value[0] === `[[...${key}]]`))) {
            value = undefined;
            delete query[key];
        }
        // query values from the proxy aren't already split into arrays
        // so make sure to normalize catch-all values
        if (value && typeof value === 'string' && defaultRouteRegex.groups[key].repeat) {
            value = value.split('/');
        }
        if (value) {
            params[key] = value;
        }
    }
    return {
        params,
        hasValidParams
    };
}
export function getServerUtils({ page, i18n, basePath, rewrites, pageIsDynamic, trailingSlash, caseSensitive }) {
    let defaultRouteRegex;
    let dynamicRouteMatcher;
    let defaultRouteMatches;
    if (pageIsDynamic) {
        defaultRouteRegex = getNamedRouteRegex(page, {
            prefixRouteKeys: false
        });
        dynamicRouteMatcher = getRouteMatcher(defaultRouteRegex);
        defaultRouteMatches = dynamicRouteMatcher(page);
    }
    function handleRewrites(req, parsedUrl) {
        const rewriteParams = {};
        let fsPathname = parsedUrl.pathname;
        const matchesPage = ()=>{
            const fsPathnameNoSlash = removeTrailingSlash(fsPathname || '');
            return fsPathnameNoSlash === removeTrailingSlash(page) || (dynamicRouteMatcher == null ? void 0 : dynamicRouteMatcher(fsPathnameNoSlash));
        };
        const checkRewrite = (rewrite)=>{
            const matcher = getPathMatch(rewrite.source + (trailingSlash ? '(/)?' : ''), {
                removeUnnamedParams: true,
                strict: true,
                sensitive: !!caseSensitive
            });
            if (!parsedUrl.pathname) return false;
            let params = matcher(parsedUrl.pathname);
            if ((rewrite.has || rewrite.missing) && params) {
                const hasParams = matchHas(req, parsedUrl.query, rewrite.has, rewrite.missing);
                if (hasParams) {
                    Object.assign(params, hasParams);
                } else {
                    params = false;
                }
            }
            if (params) {
                try {
                    // An interception rewrite might reference a dynamic param for a route the user
                    // is currently on, which wouldn't be extractable from the matched route params.
                    // This attempts to extract the dynamic params from the provided router state.
                    if (isInterceptionRouteRewrite(rewrite)) {
                        const stateHeader = req.headers[NEXT_ROUTER_STATE_TREE_HEADER];
                        if (stateHeader) {
                            params = {
                                ...getSelectedParams(parseAndValidateFlightRouterState(stateHeader)),
                                ...params
                            };
                        }
                    }
                } catch (err) {
                // this is a no-op -- we couldn't extract dynamic params from the provided router state,
                // so we'll just use the params from the route matcher
                }
                const { parsedDestination, destQuery } = prepareDestination({
                    appendParamsToQuery: true,
                    destination: rewrite.destination,
                    params: params,
                    query: parsedUrl.query
                });
                // if the rewrite destination is external break rewrite chain
                if (parsedDestination.protocol) {
                    return true;
                }
                Object.assign(rewriteParams, destQuery, params);
                Object.assign(parsedUrl.query, parsedDestination.query);
                delete parsedDestination.query;
                // for each property in parsedUrl.query, if the value is parametrized (eg :foo), look up the value
                // in rewriteParams and replace the parametrized value with the actual value
                // this is used when the rewrite destination does not contain the original source param
                // and so the value is still parametrized and needs to be replaced with the actual rewrite param
                Object.entries(parsedUrl.query).forEach(([key, value])=>{
                    if (value && typeof value === 'string' && value.startsWith(':')) {
                        const paramName = value.slice(1);
                        const actualValue = rewriteParams[paramName];
                        if (actualValue) {
                            parsedUrl.query[key] = actualValue;
                        }
                    }
                });
                Object.assign(parsedUrl, parsedDestination);
                fsPathname = parsedUrl.pathname;
                if (!fsPathname) return false;
                if (basePath) {
                    fsPathname = fsPathname.replace(new RegExp(`^${basePath}`), '') || '/';
                }
                if (i18n) {
                    const result = normalizeLocalePath(fsPathname, i18n.locales);
                    fsPathname = result.pathname;
                    parsedUrl.query.nextInternalLocale = result.detectedLocale || params.nextInternalLocale;
                }
                if (fsPathname === page) {
                    return true;
                }
                if (pageIsDynamic && dynamicRouteMatcher) {
                    const dynamicParams = dynamicRouteMatcher(fsPathname);
                    if (dynamicParams) {
                        parsedUrl.query = {
                            ...parsedUrl.query,
                            ...dynamicParams
                        };
                        return true;
                    }
                }
            }
            return false;
        };
        for (const rewrite of rewrites.beforeFiles || []){
            checkRewrite(rewrite);
        }
        if (fsPathname !== page) {
            let finished = false;
            for (const rewrite of rewrites.afterFiles || []){
                finished = checkRewrite(rewrite);
                if (finished) break;
            }
            if (!finished && !matchesPage()) {
                for (const rewrite of rewrites.fallback || []){
                    finished = checkRewrite(rewrite);
                    if (finished) break;
                }
            }
        }
        return rewriteParams;
    }
    function getParamsFromRouteMatches(routeMatchesHeader) {
        // If we don't have a default route regex, we can't get params from route
        // matches
        if (!defaultRouteRegex) return null;
        const { groups, routeKeys } = defaultRouteRegex;
        const matcher = getRouteMatcher({
            re: {
                // Simulate a RegExp match from the \`req.url\` input
                exec: (str)=>{
                    // Normalize all the prefixed query params.
                    const obj = Object.fromEntries(new URLSearchParams(str));
                    for (const [key, value] of Object.entries(obj)){
                        const normalizedKey = normalizeNextQueryParam(key);
                        if (!normalizedKey) continue;
                        obj[normalizedKey] = value;
                        delete obj[key];
                    }
                    // Use all the named route keys.
                    const result = {};
                    for (const keyName of Object.keys(routeKeys)){
                        const paramName = routeKeys[keyName];
                        // If this param name is not a valid parameter name, then skip it.
                        if (!paramName) continue;
                        const group = groups[paramName];
                        const value = obj[keyName];
                        // When we're missing a required param, we can't match the route.
                        if (!group.optional && !value) return null;
                        result[group.pos] = value;
                    }
                    return result;
                }
            },
            groups
        });
        const routeMatches = matcher(routeMatchesHeader);
        if (!routeMatches) return null;
        return routeMatches;
    }
    function normalizeQueryParams(query, routeParamKeys) {
        // this is used to pass query information in rewrites
        // but should not be exposed in final query
        delete query['nextInternalLocale'];
        for (const [key, value] of Object.entries(query)){
            const normalizedKey = normalizeNextQueryParam(key);
            if (!normalizedKey) continue;
            // Remove the prefixed key from the query params because we want
            // to consume it for the dynamic route matcher.
            delete query[key];
            routeParamKeys.add(normalizedKey);
            if (typeof value === 'undefined') continue;
            query[normalizedKey] = Array.isArray(value) ? value.map((v)=>decodeQueryPathParameter(v)) : decodeQueryPathParameter(value);
        }
    }
    return {
        handleRewrites,
        defaultRouteRegex,
        dynamicRouteMatcher,
        defaultRouteMatches,
        normalizeQueryParams,
        getParamsFromRouteMatches,
        /**
     * Normalize dynamic route params.
     *
     * @param query - The query params to normalize.
     * @param ignoreMissingOptional - Whether to ignore missing optional params.
     * @returns The normalized params and whether they are valid.
     */ normalizeDynamicRouteParams: (query, ignoreMissingOptional)=>{
            if (!defaultRouteRegex || !defaultRouteMatches) {
                return {
                    params: {},
                    hasValidParams: false
                };
            }
            return normalizeDynamicRouteParams(query, defaultRouteRegex, defaultRouteMatches, ignoreMissingOptional);
        },
        normalizeCdnUrl: (req, paramKeys)=>normalizeCdnUrl(req, paramKeys),
        interpolateDynamicPath: (pathname, params)=>interpolateDynamicPath(pathname, params, defaultRouteRegex),
        filterInternalQuery: (query, paramKeys)=>filterInternalQuery(query, paramKeys)
    };
}
export function getPreviouslyRevalidatedTags(headers, previewModeId) {
    return typeof headers[NEXT_CACHE_REVALIDATED_TAGS_HEADER] === 'string' && headers[NEXT_CACHE_REVALIDATE_TAG_TOKEN_HEADER] === previewModeId ? headers[NEXT_CACHE_REVALIDATED_TAGS_HEADER].split(',') : [];
}

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