import { getRouteParam } from "./helpers/get-route-param.js";
import { groupWorkspacesByTeam } from "./helpers/group-workspaces.js";
import { useTheme } from "./hooks/use-theme.js";
import { getActiveEnvironment } from "../../helpers/get-active-environment.js";
import { getTabDetails } from "../../helpers/get-tab-details.js";
import { slugify } from "../../helpers/slugify.js";
import { workspaceStorage } from "../../helpers/storage.js";
import { initializeAppEventHandlers } from "./app-events.js";
import { canLoadWorkspace, filterWorkspacesByTeam } from "./helpers/filter-workspaces.js";
import { computed, readonly, ref, shallowRef, watch } from "vue";
import { migrateLocalStorageToIndexDb } from "@scalar/oas-utils/migrations";
import { isDefined } from "@scalar/helpers/array/is-defined";
import { createWorkspaceEventBus } from "@scalar/workspace-store/events";
import { createSidebarState, generateReverseIndex } from "@scalar/sidebar";
import { sortByOrder } from "@scalar/helpers/array/sort-by-order";
import { createWorkspaceStore } from "@scalar/workspace-store/client";
import { generateUniqueValue } from "@scalar/workspace-store/helpers/generate-unique-value";
import { getParentEntry } from "@scalar/workspace-store/navigation";
import { createWorkspaceStorePersistence, getWorkspaceId } from "@scalar/workspace-store/persistence";
import { persistencePlugin } from "@scalar/workspace-store/plugins/client";
import { extensions } from "@scalar/workspace-store/schemas/extensions";
//#region src/v2/features/app/app-state.ts
/** Default debounce delay in milliseconds for workspace store persistence. */
var DEFAULT_DEBOUNCE_DELAY = 1e3;
/** Default sidebar width in pixels. */
var DEFAULT_SIDEBAR_WIDTH = 288;
var createAppState = async ({ router, fileLoader, fallbackThemeSlug = () => "default", customThemes = () => [], telemetryDefault }) => {
	/** Workspace event bus for handling workspace-level events. */
	const eventBus = createWorkspaceEventBus({ debug: false });
	const { workspace: persistence } = await createWorkspaceStorePersistence();
	/**
	* Run migration from localStorage to IndexedDB if needed
	* This happens once per user and transforms old data structure to new workspace format
	*/
	await migrateLocalStorageToIndexDb();
	const teamUid = ref("local");
	const namespace = ref(void 0);
	const workspaceSlug = ref(void 0);
	const documentSlug = ref(void 0);
	const method = ref(void 0);
	const path = ref(void 0);
	const exampleName = ref(void 0);
	const isSyncingWorkspace = ref(false);
	router.afterEach((to) => handleRouteChange(to));
	const currentRoute = computed(() => router.currentRoute.value ?? null);
	const activeWorkspace = shallowRef(null);
	const workspaces = ref([]);
	const filteredWorkspaces = computed(() => filterWorkspacesByTeam(workspaces.value, teamUid.value));
	const workspaceGroups = computed(() => groupWorkspacesByTeam(filteredWorkspaces.value, teamUid.value));
	const store = shallowRef(null);
	const persistedTelemetry = workspaceStorage.getTelemetry();
	const telemetry = ref(persistedTelemetry !== null ? persistedTelemetry : Boolean(telemetryDefault));
	watch(telemetry, (value) => workspaceStorage.setTelemetry(value));
	const activeDocument = computed(() => {
		return store.value?.workspace.documents[documentSlug.value ?? ""] || null;
	});
	/**
	* Merged environment variables from workspace and document levels.
	* Variables from both sources are combined, with document variables
	* taking precedence in case of naming conflicts.
	*/
	const environment = computed(() => getActiveEnvironment(store.value, activeDocument.value));
	/** Update the workspace list when the component is mounted */
	workspaces.value = await persistence.getAll().then((w) => w.map(({ teamUid, namespace, slug, name }) => ({
		id: getWorkspaceId(namespace, slug),
		teamUid,
		namespace,
		slug,
		label: name
	})));
	/**
	* Renames the currently active workspace.
	* Updates the workspace name in persistence and updates activeWorkspace if successful.
	* Returns early if namespace or workspaceSlug is not set, or if update fails.
	*/
	const renameWorkspace = async (name) => {
		const namespaceValue = namespace.value;
		const slugValue = workspaceSlug.value;
		if (!namespaceValue || !slugValue) return;
		const workspaceId = getWorkspaceId(namespaceValue, slugValue);
		if (await persistence.updateName({
			namespace: namespaceValue,
			slug: slugValue
		}, name) === void 0) return;
		workspaces.value = workspaces.value.map((workspace) => {
			if (workspace.id === workspaceId) return {
				...workspace,
				label: name
			};
			return workspace;
		});
		activeWorkspace.value = {
			id: workspaceId,
			label: name
		};
	};
	/**
	* Creates a client-side workspace store with persistence enabled for the given workspace id.
	*/
	const createClientStore = async ({ namespace, slug }) => {
		return createWorkspaceStore({
			plugins: [await persistencePlugin({
				workspaceId: getWorkspaceId(namespace, slug),
				debounceDelay: DEFAULT_DEBOUNCE_DELAY
			})],
			fileLoader
		});
	};
	/**
	* Attempts to load and activate a workspace by id.
	* Returns true when the workspace was found and activated.
	*/
	const loadWorkspace = async (namespace, slug) => {
		const workspace = await persistence.getItem({
			namespace,
			slug
		});
		if (!workspace) return { success: false };
		const client = await createClientStore({
			namespace,
			slug
		});
		client.loadWorkspace(workspace.workspace);
		activeWorkspace.value = {
			id: getWorkspaceId(workspace.namespace, workspace.slug),
			label: workspace.name
		};
		store.value = client;
		return {
			success: true,
			workspace: client.workspace
		};
	};
	/**
	* Creates and persists the default workspace with a blank draft document.
	* Used when no workspaces exist yet.
	*/
	const createAndPersistWorkspace = async ({ name, teamUid, namespace, slug }) => {
		const draftStore = createWorkspaceStore();
		await draftStore.addDocument({
			name: "drafts",
			document: {
				openapi: "3.1.0",
				info: {
					title: "Drafts",
					version: "1.0.0"
				},
				paths: { "/": { get: {} } },
				"x-scalar-original-document-hash": "drafts",
				"x-scalar-icon": "interface-edit-tool-pencil"
			}
		});
		const workspace = await persistence.setItem({
			namespace,
			slug
		}, {
			name,
			teamUid,
			workspace: draftStore.exportWorkspace()
		});
		workspaces.value.push({
			id: getWorkspaceId(workspace.namespace, workspace.slug),
			teamUid: workspace.teamUid,
			namespace: workspace.namespace,
			slug: workspace.slug,
			label: workspace.name
		});
		return workspace;
	};
	/**
	* Navigates to the overview page of the specified workspace.
	*
	* @param namespace - The workspace namespace.
	* @param slug - The unique workspace slug (identifier).
	*/
	const navigateToWorkspace = async (namespace, slug) => {
		if (!namespace || !slug) {
			await router.push("/");
			return;
		}
		await router.push({
			name: "example",
			params: {
				namespace,
				workspaceSlug: slug,
				documentSlug: "drafts",
				pathEncoded: encodeURIComponent("/"),
				method: "get",
				exampleName: "default"
			}
		});
	};
	/**
	* Creates a new workspace with the provided name.
	* - Generates a unique slug for the workspace (uses the provided slug if it is unique, otherwise generates a unique slug).
	* - Adds a default blank document ("drafts") to the workspace.
	* - Persists the workspace and navigates to it.
	*
	* Example usage:
	*   await createWorkspace({ name: 'My Awesome API' })
	*   // -> Navigates to /workspace/my-awesome-api (if available)
	*/
	const createWorkspace = async ({ teamUid, namespace, slug, name }) => {
		store.value = null;
		const newWorkspaceSlug = await generateUniqueValue({
			defaultValue: slug ?? name,
			validation: async (value) => !await persistence.has({
				namespace: namespace ?? "local",
				slug: value
			}),
			maxRetries: 100,
			transformation: slugify
		});
		if (!newWorkspaceSlug) return;
		const createdWorkspace = await createAndPersistWorkspace({
			teamUid,
			namespace,
			slug: newWorkspaceSlug,
			name
		});
		await navigateToWorkspace(createdWorkspace.namespace, createdWorkspace.slug);
		return createdWorkspace;
	};
	/**
	* Handles changing the active workspace when the workspace slug changes in the route.
	* This function:
	*  - Clears the current workspace store and sets loading state.
	*  - Attempts to load the workspace by slug.
	*    - If found, navigates to the active tab path (if available).
	*    - If not found, creates the default workspace and navigates to it.
	*/
	const changeWorkspace = async (namespace, slug) => {
		store.value = null;
		isSyncingWorkspace.value = true;
		const result = await loadWorkspace(namespace, slug);
		if (result.success) {
			const index = result.workspace["x-scalar-active-tab"] ?? 0;
			const tabs = result.workspace["x-scalar-tabs"];
			const tab = tabs?.[index];
			if (tab) await router.replace({
				path: tab.path,
				query: currentRoute.value?.query ?? {}
			});
			if (tabs && index >= tabs.length) eventBus.emit("tabs:update:tabs", { "x-scalar-active-tab": 0 });
			if (!tabs) eventBus.emit("tabs:update:tabs", {
				"x-scalar-tabs": [createTabFromRoute(currentRoute.value)],
				"x-scalar-active-tab": 0
			});
			isSyncingWorkspace.value = false;
			return;
		}
		const targetWorkspace = filteredWorkspaces.value.find((workspace) => workspace.teamUid === "local" && workspace.slug === "default") ?? filteredWorkspaces.value[0];
		if (targetWorkspace) return navigateToWorkspace(targetWorkspace.namespace, targetWorkspace.slug);
		const createResult = await createWorkspace({
			name: "Default Workspace",
			slug: "default"
		});
		isSyncingWorkspace.value = false;
		if (!createResult) return console.error("Failed to create the default workspace, something went wrong, can not load the workspace");
		sidebarState.reset();
	};
	/**
	* Sets the current team context. If the active workspace is not accessible
	* with the new team, navigates to the default workspace for that team.
	*/
	const setTeamUid = (value) => {
		teamUid.value = value;
		const workspace = filteredWorkspaces.value.find((w) => w.namespace === namespace.value && w.slug === workspaceSlug.value);
		if (workspace && canLoadWorkspace(workspace.teamUid, value)) return;
		return navigateToWorkspace("local", "default");
	};
	const entries = computed(() => {
		const activeStore = store.value;
		if (!activeStore) return [];
		const order = activeStore.workspace["x-scalar-order"] ?? Object.keys(activeStore.workspace.documents);
		return sortByOrder(Object.keys(activeStore.workspace.documents), order, (item) => item).map((doc) => activeStore.workspace.documents[doc]?.["x-scalar-navigation"]).filter(isDefined);
	});
	const sidebarState = createSidebarState(entries);
	/**
	* Generates a unique string ID for an API location, based on the document, path, method, and example.
	* Filters out undefined values and serializes the composite array into a stable string.
	*
	* @param params - An object containing document, path, method, and optional example name.
	* @returns A stringified array representing the unique location identifier.
	*
	* Example:
	*   generateId({ document: 'mydoc', path: '/users', method: 'get', example: 'default' })
	*   // => '["mydoc","/users","get","default"]'
	*/
	const generateId = ({ document, path, method, example }) => {
		return JSON.stringify([
			document,
			path,
			method,
			example
		].filter(isDefined));
	};
	/**
	* Computed index for fast lookup of sidebar nodes by their unique API location.
	*
	* - Only indexes nodes of type 'document', 'operation', or 'example'.
	* - The lookup key is a serialized array of: [documentName, operationPath, operationMethod, exampleName?].
	* - Supports precise resolution of sidebar entries given an API "location".
	*/
	const locationIndex = computed(() => generateReverseIndex({
		items: entries.value,
		nestedKey: "children",
		filter: (node) => node.type === "document" || node.type === "operation" || node.type === "example",
		getId: (node) => {
			const document = getParentEntry("document", node);
			const operation = getParentEntry("operation", node);
			return generateId({
				document: document?.name ?? "",
				path: operation?.path,
				method: operation?.method,
				example: node.type === "example" ? node.name : void 0
			});
		}
	}));
	/**
	* Looks up a sidebar entry by its unique API location.
	* - First tries to find an entry matching all provided properties (including example).
	* - If not found, falls back to matching only the operation (ignores example field).
	* This allows resolving either examples, operations, or documents as appropriate.
	*
	* @param location - Object specifying the document name, path, method, and optional example name.
	* @returns The matching sidebar entry, or undefined if none found.
	*
	* Example:
	*   const entry = getEntryByLocation({
	*     document: 'pets',
	*     path: '/pets',
	*     method: 'get',
	*     example: 'default',
	*   })
	*/
	const getEntryByLocation = (location) => {
		const entryWithExample = locationIndex.value.get(generateId(location));
		if (entryWithExample) return entryWithExample;
		return locationIndex.value.get(generateId({
			document: location.document,
			path: location.path,
			method: location.method
		}));
	};
	/**
	* Handles item selection from the sidebar and routes navigation accordingly.
	*
	* Example:
	*   handleSelectItem('id-of-entry')
	*/
	const handleSelectItem = (id) => {
		const entry = sidebarState.getEntryById(id);
		if (!entry) {
			console.warn(`Could not find sidebar entry with id ${id} to select`);
			return;
		}
		/** Close sidebar and navigate. Used for every branch that performs navigation. */
		const navigate = (route) => {
			isSidebarOpen.value = false;
			return router.push(route);
		};
		if (entry.type === "document") {
			if (sidebarState.selectedItem.value === id) {
				sidebarState.setExpanded(id, !sidebarState.isExpanded(id));
				return;
			}
			sidebarState.setSelected(id);
			sidebarState.setExpanded(id, true);
			return navigate({
				name: "document.overview",
				params: { documentSlug: entry.name }
			});
		}
		if (entry.type === "operation") {
			if (sidebarState.isSelected(id) && sidebarState.selectedItem.value !== id) {
				sidebarState.setExpanded(id, !sidebarState.isExpanded(id));
				return;
			}
			const firstExample = entry.children?.find((child) => child.type === "example");
			if (firstExample) {
				sidebarState.setSelected(firstExample.id);
				sidebarState.setExpanded(firstExample.id, true);
			} else sidebarState.setSelected(id);
			return navigate({
				name: "example",
				params: {
					documentSlug: getParentEntry("document", entry)?.name,
					pathEncoded: encodeURIComponent(entry.path),
					method: entry.method,
					exampleName: firstExample?.name ?? "default"
				}
			});
		}
		if (entry.type === "example") {
			sidebarState.setSelected(id);
			const operation = getParentEntry("operation", entry);
			return navigate({
				name: "example",
				params: {
					documentSlug: getParentEntry("document", entry)?.name,
					pathEncoded: encodeURIComponent(operation?.path ?? ""),
					method: operation?.method,
					exampleName: entry.name
				}
			});
		}
		if (entry.type === "text") return navigate({
			name: "document.overview",
			params: { documentSlug: getParentEntry("document", entry)?.name }
		});
		sidebarState.setExpanded(id, !sidebarState.isExpanded(id));
	};
	/**
	* Navigates to the currently active tab's path using the router.
	* Returns early if the workspace store or active tab is unavailable.
	*/
	const navigateToCurrentTab = async () => {
		if (!store.value) return;
		const activeTabIndex = store.value.workspace["x-scalar-active-tab"] ?? 0;
		const activeTab = store.value.workspace["x-scalar-tabs"]?.[activeTabIndex];
		if (!activeTab) return;
		await router.replace(activeTab.path);
	};
	/**
	* Rebuilds the sidebar for the given document.
	* This is used to refresh the sidebar state after structural changes (e.g. after adding or removing items).
	*
	* @param documentName - The name (id) of the document for which to rebuild the sidebar
	*/
	const rebuildSidebar = (documentName) => {
		if (documentName) store.value?.buildSidebar(documentName);
	};
	/**
	* Ensures the sidebar is refreshed after a new example is created.
	*
	* If the sidebar entry for the new example does not exist or is of a different type,
	* this will rebuild the sidebar for the current document. This helps keep the sidebar state
	* consistent (e.g., after adding a new example via the UI).
	*/
	const refreshSidebarAfterExampleCreation = (payload) => {
		const documentName = payload.documentName ?? activeDocument.value?.["x-scalar-navigation"]?.name;
		if (!documentName) return;
		const entry = getEntryByLocation({
			document: documentName,
			path: payload.path,
			method: payload.method,
			example: payload.exampleKey
		});
		if (!entry || entry.type !== "example") {
			rebuildSidebar(documentName);
			if (currentRoute.value) syncSidebar(currentRoute.value);
		}
	};
	/** Width of the sidebar, with fallback to default. */
	const sidebarWidth = computed(() => store.value?.workspace?.["x-scalar-sidebar-width"] ?? DEFAULT_SIDEBAR_WIDTH);
	/** Handler for sidebar width changes. */
	const handleSidebarWidthUpdate = (width) => store.value?.update("x-scalar-sidebar-width", width);
	/** Controls the visibility of the sidebar. */
	const isSidebarOpen = ref(false);
	/** Constants for workspace store keys */
	const TABS_KEY = "x-scalar-tabs";
	const ACTIVE_TAB_KEY = "x-scalar-active-tab";
	/**
	* Creates a tab object based on the current route and workspace state.
	* Used as a fallback when no tabs exist or when creating new tabs.
	*/
	const createTabFromRoute = (to) => {
		const method = getRouteParam("method", to);
		const path = getRouteParam("pathEncoded", to);
		const document = getRouteParam("documentSlug", to);
		return {
			...getTabDetails({
				workspace: getRouteParam("workspaceSlug", to),
				document,
				path,
				method,
				getEntryByLocation
			}),
			path: currentRoute.value?.path ?? ""
		};
	};
	const tabs = computed(() => {
		return store.value?.workspace[TABS_KEY] ?? [createTabFromRoute(currentRoute.value)];
	});
	const activeTabIndex = computed(() => {
		return store.value?.workspace[ACTIVE_TAB_KEY] ?? 0;
	});
	/**
	* Copies the URL of the tab at the given index to the clipboard.
	* Constructs the full URL using the current origin and the tab path.
	*
	* Note: Will silently fail if clipboard API is unavailable or the tab does not exist.
	*/
	const copyTabUrl = async (index) => {
		const tab = tabs.value[index];
		if (!tab) {
			console.warn(`Cannot copy URL: tab at index ${index} does not exist`);
			return;
		}
		const url = `${window.location.origin}${tab.path}`;
		try {
			await navigator.clipboard.writeText(url);
		} catch (error) {
			console.error("Failed to copy URL to clipboard:", error);
		}
	};
	/** When the route changes we need to update the active entities in the store */
	const handleRouteChange = (to) => {
		const slug = getRouteParam("workspaceSlug", to);
		const document = getRouteParam("documentSlug", to);
		const namespaceValue = getRouteParam("namespace", to);
		if (!namespaceValue || !slug) return;
		const workspace = workspaces.value.find((workspace) => workspace.slug === slug && workspace.namespace === namespaceValue);
		if (workspace && !canLoadWorkspace(workspace.teamUid, teamUid.value)) return navigateToWorkspace("local", "default");
		namespace.value = namespaceValue;
		workspaceSlug.value = slug;
		documentSlug.value = document;
		method.value = getRouteParam("method", to);
		path.value = getRouteParam("pathEncoded", to);
		exampleName.value = getRouteParam("exampleName", to);
		if (to.path !== "") workspaceStorage.setCurrentPath(to.path);
		if (getWorkspaceId(namespace.value, slug) !== activeWorkspace.value?.id) return changeWorkspace(namespace.value, slug);
		if (document && document !== store.value?.workspace[extensions.workspace.activeDocument]) store?.value?.update("x-scalar-active-document", document);
		syncTabs(to);
		syncSidebar(to);
	};
	/** Aligns the tabs with any potential slug changes */
	const syncTabs = (to) => {
		const tabs = store.value?.workspace["x-scalar-tabs"] ?? [];
		const index = store.value?.workspace["x-scalar-active-tab"] ?? 0;
		const tab = tabs[index];
		if (!tab || tab.path === to.path) return;
		tabs[index] = createTabFromRoute(to);
	};
	/** Aligns the sidebar state with any potential slug changes */
	const syncSidebar = (to) => {
		const document = getRouteParam("documentSlug", to);
		if (!document) {
			sidebarState.setSelected(null);
			return;
		}
		const entry = getEntryByLocation({
			document,
			path: getRouteParam("pathEncoded", to),
			method: getRouteParam("method", to),
			example: getRouteParam("exampleName", to)
		});
		if (entry) {
			sidebarState.setSelected(entry.id);
			sidebarState.setExpanded(entry.id, true);
		}
	};
	initializeAppEventHandlers({
		eventBus,
		router,
		store,
		navigateToCurrentTab,
		rebuildSidebar,
		onAfterExampleCreation: refreshSidebarAfterExampleCreation,
		onSelectSidebarItem: handleSelectItem,
		onCopyTabUrl: (index) => copyTabUrl(index),
		onToggleSidebar: () => isSidebarOpen.value = !isSidebarOpen.value,
		closeSidebar: () => isSidebarOpen.value = false,
		renameWorkspace
	});
	const theme = useTheme({
		fallbackThemeSlug,
		customThemes,
		store
	});
	const isDarkMode = computed(() => {
		const colorMode = store.value?.workspace["x-scalar-color-mode"] ?? "system";
		if (colorMode === "system") return window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ?? false;
		return colorMode === "dark";
	});
	return {
		store,
		sidebar: {
			state: sidebarState,
			width: sidebarWidth,
			isOpen: isSidebarOpen,
			handleSelectItem,
			handleSidebarWidthUpdate,
			getEntryByLocation
		},
		tabs: {
			state: tabs,
			activeTabIndex,
			copyTabUrl
		},
		workspace: {
			create: createWorkspace,
			workspaceList: workspaces,
			filteredWorkspaceList: filteredWorkspaces,
			workspaceGroups,
			activeWorkspace,
			navigateToWorkspace,
			isOpen: computed(() => Boolean(workspaceSlug.value && !documentSlug.value))
		},
		eventBus,
		router,
		currentRoute,
		loading: isSyncingWorkspace,
		activeEntities: {
			namespace,
			workspaceSlug,
			documentSlug,
			path,
			method,
			exampleName,
			teamUid: readonly(teamUid),
			setTeamUid
		},
		environment,
		document: activeDocument,
		isDarkMode,
		theme: {
			styles: theme.themeStyles,
			themeStyleTag: theme.themeStyleTag,
			customThemes
		},
		telemetry
	};
};
//#endregion
export { createAppState };

//# sourceMappingURL=app-state.js.map