import { functionalUpdate, isPlainObject } from "./utils.js";
import { isServer } from "@tanstack/router-core/isServer";
//#region src/scroll-restoration.ts
function getSafeSessionStorage() {
	try {
		return typeof window !== "undefined" && typeof window.sessionStorage === "object" ? window.sessionStorage : void 0;
	} catch {
		return;
	}
}
var storageKey = "tsr-scroll-restoration-v1_3";
function createScrollRestorationCache() {
	const safeSessionStorage = getSafeSessionStorage();
	if (!safeSessionStorage) return null;
	let state = {};
	try {
		const parsed = JSON.parse(safeSessionStorage.getItem("tsr-scroll-restoration-v1_3") || "{}");
		if (isPlainObject(parsed)) state = parsed;
	} catch {}
	const persist = () => {
		try {
			safeSessionStorage.setItem(storageKey, JSON.stringify(state));
		} catch {
			if (process.env.NODE_ENV !== "production") console.warn("[ts-router] Could not persist scroll restoration state to sessionStorage.");
		}
	};
	return {
		get state() {
			return state;
		},
		set: (updater) => {
			state = functionalUpdate(updater, state) || state;
		},
		persist
	};
}
var scrollRestorationCache = createScrollRestorationCache();
/**
* The default `getKey` function for `useScrollRestoration`.
* It returns the `key` from the location state or the `href` of the location.
*
* The `location.href` is used as a fallback to support the use case where the location state is not available like the initial render.
*/
var defaultGetScrollRestorationKey = (location) => {
	return location.state.__TSR_key || location.href;
};
function getCssSelector(el) {
	const path = [];
	let parent;
	while (parent = el.parentNode) {
		path.push(`${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`);
		el = parent;
	}
	return `${path.reverse().join(" > ")}`.toLowerCase();
}
function getElementScrollRestorationEntry(router, options) {
	const restoreKey = (options.getKey || defaultGetScrollRestorationKey)(router.latestLocation);
	if (options.id) return scrollRestorationCache?.state[restoreKey]?.[`[${scrollRestorationIdAttribute}="${options.id}"]`];
	const element = options.getElement?.();
	if (!element) return;
	return scrollRestorationCache?.state[restoreKey]?.[element instanceof Window ? windowScrollTarget : getCssSelector(element)];
}
var ignoreScroll = false;
var windowScrollTarget = "window";
var scrollRestorationIdAttribute = "data-scroll-restoration-id";
function setupScrollRestoration(router, force) {
	if (!scrollRestorationCache && !(isServer ?? router.isServer)) return;
	const cache = scrollRestorationCache;
	if (force ?? router.options.scrollRestoration ?? false) router.isScrollRestoring = true;
	if ((isServer ?? router.isServer) || router.isScrollRestorationSetup || !cache) return;
	router.isScrollRestorationSetup = true;
	ignoreScroll = false;
	const getKey = router.options.getScrollRestorationKey || defaultGetScrollRestorationKey;
	const trackedScrollEntries = /* @__PURE__ */ new Map();
	window.history.scrollRestoration = "manual";
	const onScroll = (event) => {
		if (ignoreScroll || !router.isScrollRestoring) return;
		if (event.target === document || event.target === window) trackedScrollEntries.set(windowScrollTarget, {
			scrollX: window.scrollX || 0,
			scrollY: window.scrollY || 0
		});
		else {
			const target = event.target;
			trackedScrollEntries.set(target, {
				scrollX: target.scrollLeft || 0,
				scrollY: target.scrollTop || 0
			});
		}
	};
	const snapshotCurrentScrollTargets = (restoreKey) => {
		if (!router.isScrollRestoring || !restoreKey || trackedScrollEntries.size === 0 || !cache) return;
		const keyEntry = cache.state[restoreKey] ||= {};
		for (const [target, position] of trackedScrollEntries) {
			let selector;
			if (target === windowScrollTarget) selector = windowScrollTarget;
			else if (target.isConnected) {
				const attrId = target.getAttribute(scrollRestorationIdAttribute);
				selector = attrId ? `[${scrollRestorationIdAttribute}="${attrId}"]` : getCssSelector(target);
			}
			if (!selector) continue;
			keyEntry[selector] = position;
		}
	};
	document.addEventListener("scroll", onScroll, true);
	router.subscribe("onBeforeLoad", (event) => {
		snapshotCurrentScrollTargets(event.fromLocation ? getKey(event.fromLocation) : void 0);
		trackedScrollEntries.clear();
	});
	window.addEventListener("pagehide", () => {
		snapshotCurrentScrollTargets(getKey(router.stores.resolvedLocation.get() ?? router.stores.location.get()));
		cache.persist();
	});
	router.subscribe("onRendered", (event) => {
		const cacheKey = getKey(event.toLocation);
		const behavior = router.options.scrollRestorationBehavior;
		const scrollToTopSelectors = router.options.scrollToTopSelectors;
		trackedScrollEntries.clear();
		if (!router.resetNextScroll) {
			router.resetNextScroll = true;
			return;
		}
		if (typeof router.options.scrollRestoration === "function" && !router.options.scrollRestoration({ location: router.latestLocation })) return;
		ignoreScroll = true;
		try {
			const elementEntries = router.isScrollRestoring ? cache.state[cacheKey] : void 0;
			let restored = false;
			if (elementEntries) for (const elementSelector in elementEntries) {
				const entry = elementEntries[elementSelector];
				if (!isPlainObject(entry)) continue;
				const { scrollX, scrollY } = entry;
				if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) continue;
				if (elementSelector === windowScrollTarget) {
					window.scrollTo({
						top: scrollY,
						left: scrollX,
						behavior
					});
					restored = true;
				} else if (elementSelector) {
					let element;
					try {
						element = document.querySelector(elementSelector);
					} catch {
						continue;
					}
					if (element) {
						element.scrollLeft = scrollX;
						element.scrollTop = scrollY;
						restored = true;
					}
				}
			}
			if (!restored) {
				const hash = router.history.location.hash.slice(1);
				if (hash) {
					const hashScrollIntoViewOptions = window.history.state?.__hashScrollIntoViewOptions ?? true;
					if (hashScrollIntoViewOptions) {
						const el = document.getElementById(hash);
						if (el) el.scrollIntoView(hashScrollIntoViewOptions);
					}
				} else {
					const scrollOptions = {
						top: 0,
						left: 0,
						behavior
					};
					window.scrollTo(scrollOptions);
					if (scrollToTopSelectors) for (const selector of scrollToTopSelectors) {
						if (selector === windowScrollTarget) continue;
						const element = typeof selector === "function" ? selector() : document.querySelector(selector);
						if (element) element.scrollTo(scrollOptions);
					}
				}
			}
		} finally {
			ignoreScroll = false;
		}
		if (router.isScrollRestoring) cache.set((state) => {
			state[cacheKey] ||= {};
			return state;
		});
	});
}
//#endregion
export { defaultGetScrollRestorationKey, getElementScrollRestorationEntry, scrollRestorationCache, setupScrollRestoration, storageKey };

//# sourceMappingURL=scroll-restoration.js.map