import { RouterLink, RouterView, START_LOCATION, createMemoryHistory, createRouter, matchedRouteKey, routeLocationKey, routerKey, routerViewLocationKey } from "vue-router";
import { config } from "@vue/test-utils";
import { computed, defineComponent, nextTick, reactive, ref } from "vue";

//#region src/routeLocation.ts
/**
* Wraps a `router.currentRoute` properties using `reactive` and computed
* properties to mimic `useRoute()` from vue-router.
*
* @param route router.currentRoute to wrap
*/
function createReactiveRouteLocation(route) {
	return reactive(Object.keys(route.value).reduce((newRoute, key) => {
		newRoute[key] = computed(() => route.value[key]);
		return newRoute;
	}, {}));
}

//#endregion
//#region src/testers/jest.ts
function getJestGlobal() {
	return typeof jest !== "undefined" && jest;
}

//#endregion
//#region src/testers/sinon.ts
function getSinonGlobal() {
	return typeof sinon !== "undefined" && sinon;
}

//#endregion
//#region src/testers/vitest.ts
function getVitestGlobal() {
	return typeof vi !== "undefined" && vi;
}

//#endregion
//#region src/autoSpy.ts
/**
* Creates a spy on a function
*
* @param fn function to spy on
* @returns [spy, mockClear]
*/
function createSpy(fn, spyFactory) {
	if (spyFactory) {
		const spy = spyFactory.create(fn);
		return [spy, () => spyFactory.reset(spy)];
	}
	const sinon$1 = getSinonGlobal();
	if (sinon$1) {
		const spy = sinon$1.spy(fn);
		return [spy, () => spy.resetHistory()];
	}
	const jest$1 = getVitestGlobal() || getJestGlobal();
	if (jest$1) {
		const spy = jest$1.fn(fn);
		return [spy, () => spy.mockClear()];
	}
	console.error(`Couldn't detect a global spy (tried jest and sinon). Make sure to provide a "spy.create" option when creating the router mock.`);
	throw new Error("No Spy Available. See https://github.com/posva/vue-router-mock#testing-libraries");
}

//#endregion
//#region src/router.ts
const EmptyView = defineComponent({
	name: "RouterMockEmptyView",
	render: () => null
});
/**
* Creates a router mock instance
*
* @param options - options to initialize the router
*/
function createRouterMock(options = {}) {
	const router = createRouter({
		history: createMemoryHistory(),
		routes: [{
			path: "/:pathMatch(.*)*",
			component: EmptyView
		}],
		...options
	});
	router.onError(() => {});
	let { runPerRouteGuards, removePerRouteGuards, runInComponentGuards, useRealNavigation, noUndeclaredRoutes, spy } = options;
	const initialLocation = options.initialLocation || START_LOCATION;
	const { push, addRoute, replace, beforeEach, beforeResolve, onError } = router;
	const [addRouteMock, addRouteMockClear] = createSpy((parentRecordName, record) => {
		record = record || parentRecordName;
		if (!runPerRouteGuards || removePerRouteGuards) delete record.beforeEnter;
		return addRoute(parentRecordName, record);
	}, spy);
	const [pushMock, pushMockClear] = createSpy((to) => {
		return consumeNextReturn(to);
	}, spy);
	const [replaceMock, replaceMockClear] = createSpy((to) => {
		return consumeNextReturn(to, { replace: true });
	}, spy);
	router.push = pushMock;
	router.replace = replaceMock;
	router.addRoute = addRouteMock;
	let guardRemovers = [];
	router.beforeEach = (...args) => {
		const removeGuard = beforeEach(...args);
		guardRemovers.push(removeGuard);
		return removeGuard;
	};
	router.beforeResolve = (...args) => {
		const removeGuard = beforeResolve(...args);
		guardRemovers.push(removeGuard);
		return removeGuard;
	};
	let onErrorRemovers = [];
	router.onError = (...args) => {
		const removeOnError = onError(...args);
		onErrorRemovers.push(removeOnError);
		return removeOnError;
	};
	function reset() {
		pushMockClear();
		replaceMockClear();
		addRouteMockClear();
		guardRemovers.forEach((remove) => remove());
		guardRemovers = [];
		onErrorRemovers.forEach((remove) => remove());
		onErrorRemovers = [];
		nextReturn = void 0;
		router.currentRoute.value = initialLocation === START_LOCATION ? START_LOCATION : router.resolve(initialLocation);
	}
	let nextReturn = void 0;
	function setNextGuardReturn(returnValue) {
		nextReturn = returnValue;
	}
	function consumeNextReturn(to, options$1 = {}) {
		if (nextReturn != null || runInComponentGuards || useRealNavigation) {
			const removeGuard = router.beforeEach(() => {
				const value = nextReturn;
				removeGuard();
				nextReturn = void 0;
				return value;
			});
			const record = router.currentRoute.value.matched[depth.value];
			if (record && !runInComponentGuards) {
				record.leaveGuards.clear();
				record.updateGuards.clear();
				Object.values(record.components || {}).forEach((component) => {
					delete component.beforeRouteUpdate;
					delete component.beforeRouteLeave;
				});
			}
			pendingNavigation = (options$1.replace ? replace : push)(to);
			pendingNavigation.catch(() => {}).finally(() => {
				pendingNavigation = void 0;
			});
			return pendingNavigation;
		}
		try {
			router.currentRoute.value = router.resolve(to);
		} catch (error) {
			if (noUndeclaredRoutes) throw error;
		}
		return Promise.resolve();
	}
	let pendingNavigation;
	function getPendingNavigation() {
		return pendingNavigation || Promise.resolve();
	}
	function setParams(params) {
		router.currentRoute.value = router.resolve({ params });
		return nextTick();
	}
	function setQuery(query) {
		router.currentRoute.value = router.resolve({ query });
		return nextTick();
	}
	function setHash(hash) {
		router.currentRoute.value = router.resolve({ hash });
		return nextTick();
	}
	const depth = ref(0);
	reset();
	return {
		...router,
		push: pushMock,
		replace: replaceMock,
		addRoute: addRouteMock,
		depth,
		setNextGuardReturn,
		getPendingNavigation,
		setParams,
		setQuery,
		setHash,
		reset
	};
}

//#endregion
//#region src/injections.ts
/**
* Inject global variables, overriding any previously inject router mock
*
* @param router - router mock to inject
*/
function injectRouterMock(router) {
	router = router || createRouterMock();
	const provides = createProvide(router);
	const route = provides[routeLocationKey];
	Object.assign(config.global.provide, provides);
	config.global.mocks.$router = router;
	config.global.mocks.$route = route;
	config.global.components.RouterView = RouterView;
	config.global.components.RouterLink = RouterLink;
	config.global.stubs.RouterLink = true;
	config.global.stubs.RouterView = true;
	return {
		router,
		route
	};
}
/**
* Creates an object of properties to be provided at your application level to
* mock what is injected by vue-router
*
* @param router - router mock instance
*/
function createProvide(router) {
	const route = createReactiveRouteLocation(router.currentRoute);
	const matchedRouteRef = computed(() => router.currentRoute.value.matched[router.depth.value]);
	return {
		[routerKey]: router,
		[routeLocationKey]: route,
		[routerViewLocationKey]: router.currentRoute,
		[matchedRouteKey]: matchedRouteRef
	};
}

//#endregion
//#region src/plugin.ts
function plugin(wrapper) {
	const router = getRouter();
	router.currentRoute.value.matched.forEach((record) => {
		for (const name in record.components) record.instances[name] = wrapper.vm;
	});
	wrapper.router = router;
	return wrapper;
}
function getRouter() {
	return config.global.provide[routerKey];
}

//#endregion
export { EmptyView, plugin as VueRouterMock, createProvide, createRouterMock, getRouter, injectRouterMock };
//# sourceMappingURL=index.js.map