"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.populateParams = populateParams;
exports.safelyDecodeURIComponent = safelyDecodeURIComponent;
exports.getUrlWithReactNavigationConcessions = getUrlWithReactNavigationConcessions;
exports.createConfig = createConfig;
exports.assertScreens = assertScreens;
exports.configRegExp = configRegExp;
exports.isDynamicPart = isDynamicPart;
exports.replacePart = replacePart;
exports.getParamValue = getParamValue;
exports.handleUrlParams = handleUrlParams;
exports.spreadParamsAcrossAllStates = spreadParamsAcrossAllStates;
exports.stripBaseUrl = stripBaseUrl;
exports.matchForEmptyPath = matchForEmptyPath;
exports.appendIsInitial = appendIsInitial;
exports.getRouteConfigSorter = getRouteConfigSorter;
exports.parseQueryParams = parseQueryParams;
exports.cleanPath = cleanPath;
exports.routePatternToRegex = routePatternToRegex;
const escape_string_regexp_1 = __importDefault(require("escape-string-regexp"));
const matchers_1 = require("../matchers");
const url_1 = require("../utils/url");
/**
 * In Expo Router, the params are available at all levels of the routing config
 * @param routes
 * @returns
 */
function populateParams(routes, params) {
    if (!routes || !params || Object.keys(params).length === 0)
        return;
    for (const route of routes) {
        Object.assign(route, { params });
    }
    return routes;
}
function safelyDecodeURIComponent(str) {
    try {
        return decodeURIComponent(str);
    }
    catch {
        return str;
    }
}
function getUrlWithReactNavigationConcessions(path, baseUrl = process.env.EXPO_BASE_URL) {
    const pathWithoutGroups = (0, matchers_1.stripGroupSegmentsFromPath)(stripBaseUrl(path, baseUrl));
    let pathname = '';
    let hash = '';
    try {
        const parsed = (0, url_1.parseUrlUsingCustomBase)(path);
        pathname = parsed.pathname;
        hash = parsed.hash;
    }
    catch {
        // Do nothing with invalid URLs.
    }
    const withoutBaseUrl = stripBaseUrl(pathname, baseUrl);
    return {
        path,
        // Make sure there is a trailing slash
        // The slashes are at the end, not the beginning
        nonstandardPathname: withoutBaseUrl.replace(/^\/+/g, '').replace(/\/+$/g, '') + '/',
        hash,
        pathWithoutGroups,
    };
}
function createConfig(screen, pattern, routeNames, config = {}) {
    const parts = [];
    let isDynamic = false;
    const isIndex = screen === 'index' || screen.endsWith('/index');
    let staticPartCount = 0;
    for (const part of pattern.split('/')) {
        if (part) {
            // If any part is dynamic, then the route is dynamic
            const isDynamicPart = part.startsWith(':') || part.startsWith('*') || part.includes('*not-found');
            isDynamic ||= isDynamicPart;
            if (!(0, matchers_1.matchGroupName)(part)) {
                parts.push(part);
                if (!isDynamicPart) {
                    staticPartCount++;
                }
            }
        }
    }
    const hasChildren = config.screens ? !!Object.keys(config.screens)?.length : false;
    const type = hasChildren ? 'layout' : isDynamic ? 'dynamic' : 'static';
    if (isIndex) {
        parts.push('index');
        staticPartCount++;
    }
    return {
        type,
        isIndex,
        hasChildren,
        parts,
        staticPartCount,
        userReadableName: [...routeNames.slice(0, -1), config.path || screen].join('/'),
        // Don't include the __root route name
        expandedRouteNames: routeNames.slice(1).flatMap((name) => {
            return name.split('/');
        }),
    };
}
function assertScreens(options) {
    if (!options?.screens) {
        throw Error("You must pass a 'screens' object to 'getStateFromPath' to generate a path.");
    }
}
function configRegExp(config) {
    return config.pattern
        ? new RegExp(`^(${config.pattern.split('/').map(formatRegexPattern).join('')})$`)
        : undefined;
}
function isDynamicPart(p) {
    return p.length > 1 && (p.startsWith(':') || p.startsWith('*'));
}
function replacePart(p) {
    return p.replace(/^[:*]/, '').replace(/\?$/, '');
}
function getParamValue(p, value) {
    if (p.startsWith('*')) {
        const values = value.split('/').filter((v) => v !== '');
        return values.length === 0 && p.endsWith('?') ? undefined : values;
    }
    else {
        return value;
    }
}
function formatRegexPattern(it) {
    // Allow spaces in file path names.
    it = it.replace(' ', '%20');
    if (it.startsWith(':')) {
        // TODO: Remove unused match group
        return `(([^/]+\\/)${it.endsWith('?') ? '?' : ''})`;
    }
    else if (it.startsWith('*')) {
        return `((.*\\/)${it.endsWith('?') ? '?' : ''})`;
    }
    // Strip groups from the matcher
    if ((0, matchers_1.matchGroupName)(it) != null) {
        // Groups are optional segments
        // this enables us to match `/bar` and `/(foo)/bar` for the same route
        // NOTE(EvanBacon): Ignore this match in the regex to avoid capturing the group
        return `(?:${(0, escape_string_regexp_1.default)(it)}\\/)?`;
    }
    return (0, escape_string_regexp_1.default)(it) + `\\/`;
}
function handleUrlParams(route, params) {
    if (params) {
        route.params = Object.assign(Object.create(null), route.params);
        for (const [name, value] of Object.entries(params)) {
            if (route.params?.[name]) {
                if (process.env.NODE_ENV !== 'production') {
                    console.warn(`Route '/${route.name}' with param '${name}' was specified both in the path and as a param, removing from path`);
                }
            }
            if (!route.params?.[name]) {
                route.params[name] = value;
                continue;
            }
        }
        if (Object.keys(route.params).length === 0) {
            delete route.params;
        }
    }
}
function spreadParamsAcrossAllStates(state, params) {
    while (state) {
        const route = state.routes[0];
        route.params = Object.assign({}, route.params, params);
    }
}
function stripBaseUrl(path, baseUrl = process.env.EXPO_BASE_URL) {
    if (process.env.NODE_ENV !== 'development') {
        if (baseUrl) {
            return path.replace(/^\/+/g, '/').replace(new RegExp(`^\\/?${(0, escape_string_regexp_1.default)(baseUrl)}`, 'g'), '');
        }
    }
    return path;
}
function matchForEmptyPath(configs) {
    // We need to add special handling of empty path so navigation to empty path also works
    // When handling empty path, we should only look at the root level config
    // NOTE(EvanBacon): We only care about matching leaf nodes.
    const leafNodes = configs
        .filter((config) => !config.hasChildren)
        .map((value) => {
        return {
            ...value,
            // Collapse all levels of group segments before testing.
            // This enables `app/(one)/(two)/index.js` to be matched.
            path: (0, matchers_1.stripGroupSegmentsFromPath)(value.path),
        };
    });
    const match = leafNodes.find((config) => 
    // NOTE(EvanBacon): Test leaf node index routes that either don't have a regex or match an empty string.
    config.path === '' && (!config.regex || config.regex.test(''))) ??
        leafNodes.find((config) => 
        // NOTE(EvanBacon): Test leaf node dynamic routes that match an empty string.
        config.path.startsWith(':') && config.regex.test('')) ??
        // NOTE(EvanBacon): Test leaf node deep dynamic routes that match a slash.
        // This should be done last to enable dynamic routes having a higher priority.
        leafNodes.find((config) => config.path.startsWith('*') && config.regex.test('/'));
    return match;
}
function appendIsInitial(initialRoutes) {
    const resolvedInitialPatterns = initialRoutes.map((route) => joinPaths(...route.parentScreens, route.initialRouteName));
    return function (config) {
        // TODO(EvanBacon): Probably a safer way to do this
        // Mark initial routes to give them potential priority over other routes that match.
        config.isInitial = resolvedInitialPatterns.includes(config.routeNames.join('/'));
        return config;
    };
}
const joinPaths = (...paths) => []
    .concat(...paths.map((p) => p.split('/')))
    .filter(Boolean)
    .join('/');
function getRouteConfigSorter(previousSegments = []) {
    return function sortConfigs(a, b) {
        // Sort config so that:
        // - the most exhaustive ones are always at the beginning
        // - patterns with wildcard are always at the end
        // If 2 patterns are same, move the one with less route names up
        // This is an error state, so it's only useful for consistent error messages
        if (a.pattern === b.pattern) {
            return b.routeNames.join('>').localeCompare(a.routeNames.join('>'));
        }
        /*
         * If one of the patterns starts with the other, it is earlier in the config sorting.
         * However, configs are a mix of route configs and layout configs
         * e.g There will be a config for `/(group)`, but maybe there isn't a `/(group)/index.tsx`
         *
         * This is because you can navigate to a directory and its navigator will determine the route
         * These routes should be later in the config sorting, as their patterns are very open
         * and will prevent routes from being matched
         *
         * Therefore before we compare segment parts, we force these layout configs later in the sorting
         *
         * NOTE(marklawlor): Is this a feature we want? I'm unsure if this is a gimmick or a feature.
         */
        if (a.pattern.startsWith(b.pattern) && !b.isIndex) {
            return -1;
        }
        if (b.pattern.startsWith(a.pattern) && !a.isIndex) {
            return 1;
        }
        /*
         * Static routes should always be higher than dynamic and layout routes.
         */
        if (a.type === 'static' && b.type !== 'static') {
            return -1;
        }
        else if (a.type !== 'static' && b.type === 'static') {
            return 1;
        }
        /*
         * If the routes have any static segments, the one the most static segments should be higher
         */
        if (a.staticPartCount !== b.staticPartCount) {
            return b.staticPartCount - a.staticPartCount;
        }
        /*
         * If both are static/dynamic or a layout file, then we check group similarity
         */
        const similarToPreviousA = previousSegments.filter((value, index) => {
            return value === a.expandedRouteNames[index] && value.startsWith('(') && value.endsWith(')');
        });
        const similarToPreviousB = previousSegments.filter((value, index) => {
            return value === b.expandedRouteNames[index] && value.startsWith('(') && value.endsWith(')');
        });
        if ((similarToPreviousA.length > 0 || similarToPreviousB.length > 0) &&
            similarToPreviousA.length !== similarToPreviousB.length) {
            // One matches more than the other, so pick the one that matches more
            return similarToPreviousB.length - similarToPreviousA.length;
        }
        /*
         * If there is not difference in similarity, then each non-group segment is compared against each other
         */
        for (let i = 0; i < Math.max(a.parts.length, b.parts.length); i++) {
            // if b is longer, b get higher priority
            if (a.parts[i] == null) {
                return 1;
            }
            // if a is longer, a get higher priority
            if (b.parts[i] == null) {
                return -1;
            }
            const aWildCard = a.parts[i].startsWith('*');
            const bWildCard = b.parts[i].startsWith('*');
            // if both are wildcard we compare next component
            if (aWildCard && bWildCard) {
                const aNotFound = a.parts[i].match(/^[*]not-found$/);
                const bNotFound = b.parts[i].match(/^[*]not-found$/);
                if (aNotFound && bNotFound) {
                    continue;
                }
                else if (aNotFound) {
                    return 1;
                }
                else if (bNotFound) {
                    return -1;
                }
                continue;
            }
            // if only a is wild card, b get higher priority
            if (aWildCard) {
                return 1;
            }
            // if only b is wild card, a get higher priority
            if (bWildCard) {
                return -1;
            }
            const aSlug = a.parts[i].startsWith(':');
            const bSlug = b.parts[i].startsWith(':');
            // if both are wildcard we compare next component
            if (aSlug && bSlug) {
                const aNotFound = a.parts[i].match(/^[*]not-found$/);
                const bNotFound = b.parts[i].match(/^[*]not-found$/);
                if (aNotFound && bNotFound) {
                    continue;
                }
                else if (aNotFound) {
                    return 1;
                }
                else if (bNotFound) {
                    return -1;
                }
                continue;
            }
            // if only a is wild card, b get higher priority
            if (aSlug) {
                return 1;
            }
            // if only b is wild card, a get higher priority
            if (bSlug) {
                return -1;
            }
        }
        /*
         * Both configs are identical in specificity and segments count/type
         * Try and sort by initial instead.
         *
         * TODO: We don't differentiate between the default initialRoute and group specific default routes
         *
         * const unstable_settings = {
         *   "group": {
         *     initialRouteName: "article"
         *  }
         * }
         *
         * "article" will be ranked higher because its an initialRoute for a group - even if not your not currently in
         * that group. The current work around is to ways provide initialRouteName for all groups
         */
        if (a.isInitial && !b.isInitial) {
            return -1;
        }
        else if (!a.isInitial && b.isInitial) {
            return 1;
        }
        return b.parts.length - a.parts.length;
    };
}
function parseQueryParams(path, route, parseConfig, hash) {
    const searchParams = (0, url_1.parseUrlUsingCustomBase)(path).searchParams;
    const params = Object.create(null);
    if (hash) {
        params['#'] = hash.slice(1);
    }
    for (const name of searchParams.keys()) {
        if (route.params?.[name]) {
            if (process.env.NODE_ENV !== 'production') {
                console.warn(`Route '/${route.name}' with param '${name}' was specified both in the path and as a param, removing from path`);
            }
        }
        else {
            const values = parseConfig?.hasOwnProperty(name)
                ? searchParams.getAll(name).map((value) => parseConfig[name](value))
                : searchParams.getAll(name);
            // searchParams.getAll returns an array.
            // if we only have a single value, and its not an array param, we need to extract the value
            params[name] = values.length === 1 ? values[0] : values;
        }
    }
    return Object.keys(params).length ? params : undefined;
}
function cleanPath(path) {
    path = path
        // let remaining = path
        // END FORK
        .replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
        .replace(/^\//, '') // Remove extra leading slash
        .replace(/\?.*$/, ''); // Remove query params which we will handle later
    // Make sure there is a trailing slash
    return path.endsWith('/') ? path : `${path}/`;
}
function routePatternToRegex(pattern) {
    return new RegExp(`^(${pattern
        .split('/')
        .map((it) => {
        if (it.startsWith('(') && it.endsWith(')')) {
            return `${it}?`;
        }
        else if (it.startsWith(':')) {
            return `(([^/]+\\/)${it.endsWith('?') ? '?' : ''})`;
        }
        return `${it === '*' ? '.*' : (0, escape_string_regexp_1.default)(it)}\\/`;
    })
        .join('')})`);
}
//# sourceMappingURL=getStateFromPath-forks.js.map