import { createControlledPromise } from "../utils.js";
import { invariant } from "../invariant.js";
import { isNotFound } from "../not-found.js";
import { hydrateSsrMatchId } from "./ssr-match-id.js";
//#region src/ssr/ssr-client.ts
function hydrateMatch(match, deyhydratedMatch) {
	match.id = deyhydratedMatch.i;
	match.__beforeLoadContext = deyhydratedMatch.b;
	match.loaderData = deyhydratedMatch.l;
	match.status = deyhydratedMatch.s;
	match.ssr = deyhydratedMatch.ssr;
	match.updatedAt = deyhydratedMatch.u;
	match.error = deyhydratedMatch.e;
	if (deyhydratedMatch.g !== void 0) match.globalNotFound = deyhydratedMatch.g;
}
async function hydrate(router) {
	if (!window.$_TSR) {
		if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!");
		invariant();
	}
	const serializationAdapters = router.options.serializationAdapters;
	if (serializationAdapters?.length) {
		const fromSerializableMap = /* @__PURE__ */ new Map();
		serializationAdapters.forEach((adapter) => {
			fromSerializableMap.set(adapter.key, adapter.fromSerializable);
		});
		window.$_TSR.t = fromSerializableMap;
		window.$_TSR.buffer.forEach((script) => script());
	}
	window.$_TSR.initialized = true;
	if (!window.$_TSR.router) {
		if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!");
		invariant();
	}
	const dehydratedRouter = window.$_TSR.router;
	dehydratedRouter.matches.forEach((dehydratedMatch) => {
		dehydratedMatch.i = hydrateSsrMatchId(dehydratedMatch.i);
	});
	if (dehydratedRouter.lastMatchId) dehydratedRouter.lastMatchId = hydrateSsrMatchId(dehydratedRouter.lastMatchId);
	const { manifest, dehydratedData, lastMatchId } = dehydratedRouter;
	router.ssr = { manifest };
	const nonce = document.querySelector("meta[property=\"csp-nonce\"]")?.content;
	router.options.ssr = { nonce };
	const matches = router.matchRoutes(router.stores.location.get());
	const routeChunkPromise = Promise.all(matches.map((match) => router.loadRouteChunk(router.looseRoutesById[match.routeId])));
	function setMatchForcePending(match) {
		const pendingMinMs = router.looseRoutesById[match.routeId].options.pendingMinMs ?? router.options.defaultPendingMinMs;
		if (pendingMinMs) {
			const minPendingPromise = createControlledPromise();
			match._nonReactive.minPendingPromise = minPendingPromise;
			match._forcePending = true;
			setTimeout(() => {
				minPendingPromise.resolve();
				router.updateMatch(match.id, (prev) => {
					prev._nonReactive.minPendingPromise = void 0;
					return {
						...prev,
						_forcePending: void 0
					};
				});
			}, pendingMinMs);
		}
	}
	function setRouteSsr(match) {
		const route = router.looseRoutesById[match.routeId];
		if (route) route.options.ssr = match.ssr;
	}
	let firstNonSsrMatchIndex = void 0;
	matches.forEach((match) => {
		const dehydratedMatch = dehydratedRouter.matches.find((d) => d.i === match.id);
		if (!dehydratedMatch) {
			match._nonReactive.dehydrated = false;
			match.ssr = false;
			setRouteSsr(match);
			return;
		}
		hydrateMatch(match, dehydratedMatch);
		setRouteSsr(match);
		match._nonReactive.dehydrated = match.ssr !== false;
		if (match.ssr === "data-only" || match.ssr === false) {
			if (firstNonSsrMatchIndex === void 0) {
				firstNonSsrMatchIndex = match.index;
				setMatchForcePending(match);
			}
		}
	});
	router.stores.setMatches(matches);
	await router.options.hydrate?.(dehydratedData);
	const activeMatches = router.stores.matches.get();
	const location = router.stores.location.get();
	await Promise.all(activeMatches.map(async (match) => {
		try {
			const route = router.looseRoutesById[match.routeId];
			const parentContext = activeMatches[match.index - 1]?.context ?? router.options.context;
			if (route.options.context) {
				const contextFnContext = {
					deps: match.loaderDeps,
					params: match.params,
					context: parentContext ?? {},
					location,
					navigate: (opts) => router.navigate({
						...opts,
						_fromLocation: location
					}),
					buildLocation: router.buildLocation,
					cause: match.cause,
					abortController: match.abortController,
					preload: false,
					matches,
					routeId: route.id
				};
				match.__routeContext = route.options.context(contextFnContext) ?? void 0;
			}
			match.context = {
				...parentContext,
				...match.__routeContext,
				...match.__beforeLoadContext
			};
			const assetContext = {
				ssr: router.options.ssr,
				matches: activeMatches,
				match,
				params: match.params,
				loaderData: match.loaderData
			};
			const headFnContent = await route.options.head?.(assetContext);
			const scripts = await route.options.scripts?.(assetContext);
			match.meta = headFnContent?.meta;
			match.links = headFnContent?.links;
			match.headScripts = headFnContent?.scripts;
			match.styles = headFnContent?.styles;
			match.scripts = scripts;
		} catch (err) {
			if (isNotFound(err)) {
				match.error = { isNotFound: true };
				console.error(`NotFound error during hydration for routeId: ${match.routeId}`, err);
			} else {
				match.error = err;
				console.error(`Error during hydration for route ${match.routeId}:`, err);
				throw err;
			}
		}
	}));
	const isSpaMode = matches[matches.length - 1].id !== lastMatchId;
	if (!matches.some((m) => m.ssr === false) && !isSpaMode) {
		matches.forEach((match) => {
			match._nonReactive.dehydrated = void 0;
		});
		router.stores.resolvedLocation.set(router.stores.location.get());
		return routeChunkPromise;
	}
	const loadPromise = Promise.resolve().then(() => router.load()).catch((err) => {
		console.error("Error during router hydration:", err);
	});
	if (isSpaMode) {
		const match = matches[1];
		if (!match) {
			if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: Expected to find a match below the root match in SPA mode.");
			invariant();
		}
		setMatchForcePending(match);
		match._displayPending = true;
		match._nonReactive.displayPendingPromise = loadPromise;
		loadPromise.then(() => {
			router.batch(() => {
				if (router.stores.status.get() === "pending") {
					router.stores.status.set("idle");
					router.stores.resolvedLocation.set(router.stores.location.get());
				}
				router.updateMatch(match.id, (prev) => ({
					...prev,
					_displayPending: void 0,
					displayPendingPromise: void 0
				}));
			});
		});
	}
	return routeChunkPromise;
}
//#endregion
export { hydrate };

//# sourceMappingURL=ssr-client.js.map