async function navigate({
	type,
	url,
	popped,
	keepfocus,
	noscroll,
	replace_state,
	state = {},
	redirect_count = 0,
	nav_token = {},
	accept = noop,
	block = noop
}) {
	const prev_token = token;
	token = nav_token;

	const intent = await get_navigation_intent(url, false);
	const nav =
		type === 'enter'
			? create_navigation(current, intent, url, type)
			: _before_navigate({ url, type, delta: popped?.delta, intent });

	if (!nav) {
		block();
		if (token === nav_token) token = prev_token;
		return;
	}

	// store this before calling `accept()`, which may change the index
	const previous_history_index = current_history_index;
	const previous_navigation_index = current_navigation_index;

	accept();

	is_navigating = true;

	if (started && nav.navigation.type !== 'enter') {
		stores.navigating.set((navigating.current = nav.navigation));
	}

	let navigation_result = intent && (await load_route(intent));

	if (!navigation_result) {
		if (is_external_url(url, base, app.hash)) {
			if (DEV && app.hash) {
				// Special case for hash mode during DEV: If someone accidentally forgets to use a hash for the link,
				// they would end up here in an endless loop. Fall back to error page in that case
				navigation_result = await server_fallback(
					url,
					{ id: null },
					await handle_error(
						new SvelteKitError(
							404,
							'Not Found',
							`Not found: ${url.pathname} (did you forget the hash?)`
						),
						{
							url,
							params: {},
							route: { id: null }
						}
					),
					404
				);
			} else {
				return await native_navigation(url);
			}
		} else {
			navigation_result = await server_fallback(
				url,
				{ id: null },
				await handle_error(new SvelteKitError(404, 'Not Found', `Not found: ${url.pathname}`), {
					url,
					params: {},
					route: { id: null }
				}),
				404
			);
		}
	}

	// if this is an internal navigation intent, use the normalized
	// URL for the rest of the function
	url = intent?.url || url;

	// abort if user navigated during update
	if (token !== nav_token) {
		nav.reject(new Error('navigation aborted'));
		return false;
	}

	if (navigation_result.type === 'redirect') {
		// whatwg fetch spec https://fetch.spec.whatwg.org/#http-redirect-fetch says to error after 20 redirects
		if (redirect_count >= 20) {
			navigation_result = await load_root_error_page({
				status: 500,
				error: await handle_error(new Error('Redirect loop'), {
					url,
					params: {},
					route: { id: null }
				}),
				url,
				route: { id: null }
			});
		} else {
			await _goto(new URL(navigation_result.location, url).href, {}, redirect_count + 1, nav_token);
			return false;
		}
	} else if (/** @type {number} */ (navigation_result.props.page.status) >= 400) {
		const updated = await stores.updated.check();
		if (updated) {
			// Before reloading, try to update the service worker if it exists
			await update_service_worker();
			await native_navigation(url);
		}
	}

	// reset invalidation only after a finished navigation. If there are redirects or
	// additional invalidations, they should get the same invalidation treatment
	reset_invalidation();

	updating = true;

	update_scroll_positions(previous_history_index);
	capture_snapshot(previous_navigation_index);

	// ensure the url pathname matches the page's trailing slash option
	if (navigation_result.props.page.url.pathname !== url.pathname) {
		url.pathname = navigation_result.props.page.url.pathname;
	}

	state = popped ? popped.state : state;

	if (!popped) {
		// this is a new navigation, rather than a popstate
		const change = replace_state ? 0 : 1;

		const entry = {
			[HISTORY_INDEX]: (current_history_index += change),
			[NAVIGATION_INDEX]: (current_navigation_index += change),
			[STATES_KEY]: state
		};

		const fn = replace_state ? history.replaceState : history.pushState;
		fn.call(history, entry, '', url);

		if (!replace_state) {
			clear_onward_history(current_history_index, current_navigation_index);
		}
	}

	// reset preload synchronously after the history state has been set to avoid race conditions
	load_cache = null;

	navigation_result.props.page.state = state;

	if (started) {
		current = navigation_result.state;

		// reset url before updating page store
		if (navigation_result.props.page) {
			navigation_result.props.page.url = url;
		}

		const after_navigate = (
			await Promise.all(
				Array.from(on_navigate_callbacks, (fn) =>
					fn(/** @type {import('@sveltejs/kit').OnNavigate} */ (nav.navigation))
				)
			)
		).filter(/** @returns {value is () => void} */ (value) => typeof value === 'function');

		if (after_navigate.length > 0) {
			function cleanup() {
				after_navigate.forEach((fn) => {
					after_navigate_callbacks.delete(fn);
				});
			}

			after_navigate.push(cleanup);

			after_navigate.forEach((fn) => {
				after_navigate_callbacks.add(fn);
			});
		}

		root.$set(navigation_result.props);
		update(navigation_result.props.page);
		has_navigated = true;
	} else {
		initialize(navigation_result, target, false);
	}

	const { activeElement } = document;

	// need to render the DOM before we can scroll to the rendered elements and do focus management
	await tick();

	// we reset scroll before dealing with focus, to avoid a flash of unscrolled content
	const scroll = popped ? popped.scroll : noscroll ? scroll_state() : null;

	if (autoscroll) {
		const deep_linked = url.hash && document.getElementById(get_id(url));
		if (scroll) {
			scrollTo(scroll.x, scroll.y);
		} else if (deep_linked) {
			// Here we use `scrollIntoView` on the element instead of `scrollTo`
			// because it natively supports the `scroll-margin` and `scroll-behavior`
			// CSS properties.
			deep_linked.scrollIntoView();
		} else {
			scrollTo(0, 0);
		}
	}

	const changed_focus =
		// reset focus only if any manual focus management didn't override it
		document.activeElement !== activeElement &&
		// also refocus when activeElement is body already because the
		// focus event might not have been fired on it yet
		document.activeElement !== document.body;

	if (!keepfocus && !changed_focus) {
		reset_focus(url);
	}

	autoscroll = true;

	if (navigation_result.props.page) {
		Object.assign(page, navigation_result.props.page);
	}

	is_navigating = false;

	if (type === 'popstate') {
		restore_snapshot(current_navigation_index);
	}

	nav.fulfil(undefined);

	after_navigate_callbacks.forEach((fn) =>
		fn(/** @type {import('@sveltejs/kit').AfterNavigate} */ (nav.navigation))
	);

	stores.navigating.set((navigating.current = null));

	updating = false;
}
@param {{
  type: import('@sveltejs/kit').NavigationType;
  url: URL;
  popped?: {
    state: Record<string, any>;
    scroll: { x: number, y: number };
    delta: number;
  };
  keepfocus?: boolean;
  noscroll?: boolean;
  replace_state?: boolean;
  state?: Record<string, any>;
  redirect_count?: number;
  nav_token?: {};
  accept?: () => void;
  block?: () => void;
}} opts