import { n as useFileDialog } from "./hooks-BvZxH4XB.js";
import { n as slugify } from "./TheCommandPalette-Dl9TAeke.js";
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, guardReactiveProps, mergeProps, nextTick, normalizeClass, normalizeProps, onMounted, openBlock, ref, renderSlot, toDisplayString, unref, vModelText, watch, withCtx, withDirectives, withKeys, withModifiers } from "vue";
import { ScalarButton, ScalarCodeBlock, ScalarIcon, ScalarToggle, ScalarTooltip, useBindCx, useLoadingState } from "@scalar/components";
import { useToasts } from "@scalar/use-toasts";
import { useRouter } from "vue-router";
import { json, yaml } from "@scalar/oas-utils/helpers";
import { convert } from "@scalar/postman-to-openapi";
import { isObject } from "@scalar/helpers/object/is-object";
import { createWorkspaceStore } from "@scalar/workspace-store/client";
import { generateUniqueValue } from "@scalar/workspace-store/helpers/generate-unique-value";
import { isLocalUrl as isLocalUrl$1 } from "@scalar/helpers/url/is-local-url";
import { isValidUrl as isValidUrl$1 } from "@scalar/helpers/url/is-valid-url";
import { parseJson, parseYaml } from "@scalar/json-magic/bundle/plugins/browser";
//#region src/v2/features/command-palette/components/CommandActionForm.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$2 = { class: "flex gap-2" };
var _hoisted_2$1 = { class: "flex max-h-8 flex-1" };
//#endregion
//#region src/v2/features/command-palette/components/CommandActionForm.vue
var CommandActionForm_default = /* @__PURE__ */ defineComponent({
	name: "CommandActionForm",
	props: {
		loader: {},
		disabled: {
			type: Boolean,
			default: false
		}
	},
	emits: [
		"submit",
		"cancel",
		"back"
	],
	setup(__props, { emit: __emit }) {
		const emit = __emit;
		const { cx } = useBindCx();
		/** Handle form submission, only emit if not disabled */
		const handleSubmit = () => {
			if (!__props.disabled) emit("submit");
		};
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("form", {
				class: "flex w-full flex-col gap-3",
				onKeydown: _cache[0] || (_cache[0] = withKeys(withModifiers(() => {}, ["stop"]), ["enter"])),
				onSubmit: withModifiers(handleSubmit, ["prevent", "stop"])
			}, [createElementVNode("div", normalizeProps(guardReactiveProps(unref(cx)("relative flex min-h-20 flex-col rounded"))), [renderSlot(_ctx.$slots, "default")], 16), createElementVNode("div", _hoisted_1$2, [createElementVNode("div", _hoisted_2$1, [renderSlot(_ctx.$slots, "options")]), createVNode(unref(ScalarButton), {
				class: "max-h-8 px-3 text-xs",
				disabled: __props.disabled,
				loader: __props.loader,
				type: "submit"
			}, {
				default: withCtx(() => [renderSlot(_ctx.$slots, "submit", {}, () => [_cache[1] || (_cache[1] = createTextVNode("Continue", -1))])]),
				_: 3
			}, 8, ["disabled", "loader"])])], 32);
		};
	}
});
//#endregion
//#region src/v2/features/command-palette/components/CommandActionInput.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$1 = ["placeholder"];
//#endregion
//#region src/v2/features/command-palette/components/CommandActionInput.vue
var CommandActionInput_default = /* @__PURE__ */ defineComponent({
	name: "CommandActionInput",
	inheritAttrs: false,
	props: {
		modelValue: {},
		placeholder: {},
		autofocus: {
			type: Boolean,
			default: true
		}
	},
	emits: ["update:modelValue", "delete"],
	setup(__props, { emit: __emit }) {
		const emit = __emit;
		const input = ref(null);
		/**
		* Two-way binding computed property for v-model support.
		* Ensures we always have a string value even if modelValue is undefined.
		*/
		const model = computed({
			get: () => __props.modelValue ?? "",
			set: (value) => emit("update:modelValue", value)
		});
		/**
		* Auto-focus the input after mounting if autofocus prop is true.
		* Uses nextTick to ensure the DOM is ready before focusing.
		*/
		onMounted(() => {
			nextTick(() => {
				if (__props.autofocus) input.value?.focus();
			});
		});
		/**
		* Handles Enter key to submit the parent form.
		* Shift+Enter is ignored to allow multi-line input if needed.
		*/
		const handleEnter = (event) => {
			if (event.shiftKey || !event.target) return;
			event.preventDefault();
			const target = event.target;
			const submitEvent = new Event("submit", { cancelable: true });
			target.form?.dispatchEvent(submitEvent);
		};
		/**
		* Handles Backspace key when input is empty.
		* Emits delete event to allow parent to navigate back or undo the last action.
		*/
		const handleDelete = (event) => {
			if (model.value !== "") return;
			event.preventDefault();
			event.stopPropagation();
			emit("delete", event);
		};
		return (_ctx, _cache) => {
			return withDirectives((openBlock(), createElementBlock("textarea", mergeProps({
				id: "command-action-input",
				ref_key: "input",
				ref: input,
				"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => model.value = $event),
				class: "min-h-8 w-full flex-1 resize-none border border-transparent py-1.5 pl-8.5 text-sm outline-none focus:border-b-1",
				placeholder: __props.placeholder ?? "",
				wrap: "hard"
			}, _ctx.$attrs, { onKeydown: [withKeys(handleDelete, ["delete"]), withKeys(handleEnter, ["enter"])] }), null, 16, _hoisted_1$1)), [[vModelText, model.value]]);
		};
	}
});
//#endregion
//#region src/v2/helpers/is-url.ts
var isUrl = (input) => {
	return (input.startsWith("http://") || input.startsWith("https://")) && isValidUrl$1(input);
};
//#endregion
//#region src/v2/features/command-palette/helpers/get-openapi-document-details.ts
/**
* Extracts the title from the info object of an API description.
* Returns undefined if the title is not a string or does not exist.
*/
var extractTitle = (info) => {
	return typeof info?.title === "string" ? info.title : void 0;
};
/**
* Attempts to extract OpenAPI document details from a parsed document.
* Checks for both OpenAPI and Swagger specifications.
*/
var parseDocumentDetails = (document, type) => {
	if (typeof document.openapi === "string") return {
		version: `OpenAPI ${document.openapi}`,
		type,
		title: extractTitle(document.info)
	};
	if (typeof document.swagger === "string") return {
		version: `Swagger ${document.swagger}`,
		type,
		title: extractTitle(document.info)
	};
};
/**
* Attempts to parse the input string with the given parser function.
* Returns undefined if parsing fails.
*/
var tryParse = (input, parser, type) => {
	try {
		return parseDocumentDetails(parser(input), type);
	} catch {
		return;
	}
};
/**
* Extracts OpenAPI or Swagger specification details from a string.
* Attempts to parse as JSON first, then YAML if JSON parsing fails.
*
* Returns undefined if:
* - Input is null, empty, or a URL
* - Input is not a valid JSON or YAML document
* - Document does not contain OpenAPI or Swagger version information
*/
var getOpenApiDocumentDetails = (input) => {
	if (!input || isUrl(input)) return;
	const jsonResult = tryParse(input, json.parse, "json");
	if (jsonResult) return jsonResult;
	return tryParse(input, yaml.parse, "yaml");
};
//#endregion
//#region src/v2/features/command-palette/helpers/is-postman-collection.ts
/**
* Checks if the given string content is a Postman collection.
*
* Validates by checking for the `_postman_id` in the info object
* and ensuring the schema URL's host matches 'schema.getpostman.com'.
*
* @param content - The JSON string which may be a Postman collection
* @returns True if the content is a valid Postman collection
*/
var isPostmanCollection = (content) => {
	try {
		const parsed = JSON.parse(content);
		const hasPostmanId = parsed.info?._postman_id !== void 0;
		const hasValidSchema = parsed.info?.schema && new URL(parsed.info.schema).host === "schema.getpostman.com";
		return hasPostmanId && hasValidSchema;
	} catch {
		return false;
	}
};
//#endregion
//#region src/v2/features/command-palette/helpers/get-postman-document-details.ts
/**
* Extracts document details from a Postman collection JSON string.
*
* We parse and validate in a single pass to avoid the performance cost
* of parsing the JSON twice.
*
* @param content - The JSON string representing the Postman collection
* @returns The collection details if valid, null otherwise
*/
var getPostmanDocumentDetails = (content) => {
	if (!isPostmanCollection(content)) return null;
	try {
		const parsed = JSON.parse(content);
		return {
			type: "json",
			title: parsed.info?.name || "Postman Collection",
			version: parsed.info?.version || "1.0"
		};
	} catch {
		return null;
	}
};
//#endregion
//#region src/v2/features/command-palette/helpers/generate-unique-slug.ts
/**
* Generates a unique slug for an imported document based on its title.
*
* This ensures the imported document does not conflict with existing documents
* by appending a number suffix if necessary (e.g., "my-api", "my-api-1", "my-api-2").
*
* The function will retry up to 100 times to find a unique slug. If all attempts fail,
* it returns null, which should be handled as an import error.
*
* @param defaultValue - The original document title to base the slug on
* @param currentDocuments - Set of existing document slugs to check against
*
* @returns Promise resolving to a unique slug, or null if unable to generate one
*/
var generateUniqueSlug = async (defaultValue, currentDocuments) => {
	return await generateUniqueValue({
		defaultValue: defaultValue ?? "default",
		validation: (value) => !currentDocuments.has(value),
		maxRetries: 100,
		transformation: slugify
	});
};
//#endregion
//#region src/v2/features/command-palette/helpers/import-document-to-workspace.ts
/**
* Imports a single document from a workspace state into the current workspace.
*
* This function handles the complete import process including slug generation,
* document loading, and sidebar updates. It ensures the imported document does not
* conflict with existing documents by generating a unique slug.
*
* The meta from the importing workspace is intentionally excluded to preserve
* the current workspace settings and avoid unintended configuration changes.
*
* @param workspaceStore - The active workspace store where the document will be imported
* @param workspaceState - The in-memory workspace containing the document to import
* @param name - The original slug/name of the document in the source workspace state
*
* @returns Promise that resolves to the slug of the imported document
*/
var importDocumentToWorkspace = async ({ workspaceStore, workspaceState, name }) => {
	if (!workspaceStore) return {
		ok: false,
		error: "Workspace store is not available"
	};
	const importingDocument = workspaceState.documents[name];
	if (!importingDocument) return {
		ok: false,
		error: "Importing document not found in workspace state"
	};
	const currentDocuments = new Set(Object.keys(workspaceStore.workspace.documents));
	/**
	* Generate a unique slug to avoid naming conflicts with existing documents.
	* The slug is based on the document title but modified if necessary to ensure uniqueness.
	*/
	const slug = await generateUniqueSlug(importingDocument.info.title || "default", currentDocuments);
	if (!slug) return {
		ok: false,
		error: "Failed to generate a unique slug for the importing document"
	};
	/**
	* Load the document along with all its associated data structures.
	* We intentionally pass an empty meta object to preserve the current workspace settings.
	*/
	workspaceStore.loadWorkspace({
		meta: {},
		documents: { [slug]: importingDocument },
		intermediateDocuments: { [slug]: workspaceState.intermediateDocuments[name] ?? {} },
		originalDocuments: { [slug]: workspaceState.originalDocuments[name] ?? {} },
		overrides: { [slug]: workspaceState.overrides[name] ?? {} },
		history: {},
		auth: {}
	});
	/**
	* Rebuild the sidebar to update all internal references from the old slug to the new one.
	* This ensures the document appears correctly in the sidebar navigation.
	*/
	workspaceStore.buildSidebar(slug);
	/**
	* Persist the imported document and the changes made during sidebar rebuilding.
	* Without this, the import would be lost on page refresh.
	*/
	await workspaceStore.saveDocument(slug);
	return {
		ok: true,
		slug
	};
};
//#endregion
//#region src/v2/features/command-palette/helpers/get-openapi-from-postman.ts
/**
* Converts a Postman collection JSON string to an OpenAPI specification document.
*
* This function transforms a Postman collection into a valid OpenAPI 3.1 document
* that can be used throughout the application. The convert function handles parsing
* and transformation internally.
*
* Returns null if the conversion fails due to invalid Postman collection structure
*/
var getOpenApiFromPostman = (postmanJson) => {
	try {
		return convert(postmanJson);
	} catch {
		return null;
	}
};
//#endregion
//#region src/v2/features/command-palette/helpers/load-document-from-source.ts
/**
* Loader plugin to detect and convert Postman collections into OpenAPI documents
*/
var readPostmanCollection = () => {
	return {
		type: "loader",
		validate: isPostmanCollection,
		exec: (source) => {
			try {
				const document = getOpenApiFromPostman(source);
				if (document) return Promise.resolve({
					ok: true,
					data: document,
					raw: source
				});
				return Promise.resolve({ ok: false });
			} catch {
				return Promise.resolve({ ok: false });
			}
		}
	};
};
/**
* Attempts to add a document to the workspace from a given source, which may be a URL or raw content.
*
* - If the source is a URL, adds the document by its URL and includes a watch mode flag as metadata.
* - If the source is a Postman collection, transforms it to OpenAPI and adds it as a document.
* - For other raw sources (such as pasted JSON or YAML), attempts to normalize and add them as a document.
*
* @param workspaceStore The workspace store where the document will be added.
* @param source The document source (URL, Postman Collection, JSON, or YAML string).
* @param name The display name for the new document.
* @param watchMode Whether to enable watch mode (applies only to URL sources).
* @returns Promise resolving to true if the document was successfully added, or false if the operation failed.
*/
var loadDocumentFromSource = async (workspaceStore, importEventData, name, watchMode) => {
	const { source, type } = importEventData;
	if (!source) return false;
	if (type === "url") return await workspaceStore.addDocument({
		name,
		url: source,
		meta: { "x-scalar-watch-mode": watchMode }
	});
	if (type === "file") return await workspaceStore.addDocument({
		name,
		path: source
	});
	const loader = [
		readPostmanCollection(),
		parseJson(),
		parseYaml()
	].find((l) => l.validate(source));
	if (!loader) return false;
	const result = await loader.exec(source);
	if (!result.ok) return false;
	if (!isObject(result.data)) return false;
	if (!await workspaceStore.addDocument({
		name,
		document: result.data
	})) return false;
	return true;
};
//#endregion
//#region src/v2/features/command-palette/components/WatchModeToggle.vue
var WatchModeToggle_default = /* @__PURE__ */ defineComponent({
	__name: "WatchModeToggle",
	props: {
		modelValue: { type: Boolean },
		disabled: {
			type: Boolean,
			default: false
		}
	},
	emits: ["update:modelValue"],
	setup(__props, { emit: __emit }) {
		const emit = __emit;
		/** Two-way binding computed property for v-model support */
		const model = computed({
			get: () => __props.modelValue,
			set: (value) => emit("update:modelValue", value)
		});
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("label", {
				class: normalizeClass(["text-c-2 flex items-center gap-2 rounded p-3 py-1.5 text-sm select-none", __props.disabled ? "cursor-default" : "cursor-pointer"]),
				for: "watch-toggle"
			}, [createElementVNode("span", { class: normalizeClass(["text-c-1 flex items-center gap-1 text-xs font-medium", { "text-c-3": !__props.modelValue }]) }, [createVNode(unref(ScalarIcon), {
				icon: "Watch",
				size: "sm"
			}), _cache[1] || (_cache[1] = createTextVNode(" Watch Mode ", -1))], 2), createVNode(unref(ScalarToggle), {
				id: "watch-toggle",
				modelValue: model.value,
				"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => model.value = $event),
				disabled: __props.disabled
			}, null, 8, ["modelValue", "disabled"])], 2);
		};
	}
});
//#endregion
//#region src/v2/features/command-palette/components/CommandPaletteImport.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1 = { class: "flex justify-between" };
var _hoisted_2 = { class: "flex w-full flex-row items-center justify-between gap-3" };
//#endregion
//#region src/v2/features/command-palette/components/CommandPaletteImport.vue
var CommandPaletteImport_default = /* @__PURE__ */ defineComponent({
	name: "CommandPaletteImport",
	props: {
		workspaceStore: {},
		eventBus: {},
		fileLoader: {}
	},
	emits: ["close", "back"],
	setup(__props, { emit: __emit }) {
		const emit = __emit;
		const { toast } = useToasts();
		const router = useRouter();
		const loader = useLoadingState();
		const inputContent = ref("");
		const watchMode = ref(false);
		/** Check if the input content is a URL */
		const isUrlInput = computed(() => isUrl(inputContent.value));
		const isLocalUrlInput = computed(() => isUrlInput.value && isLocalUrl$1(inputContent.value));
		/** Get document details based on the content type (Postman or OpenAPI) */
		const documentDetails = computed(() => {
			if (isPostmanCollection(inputContent.value)) return getPostmanDocumentDetails(inputContent.value);
			return getOpenApiDocumentDetails(inputContent.value);
		});
		/** Get the document type for syntax highlighting */
		const documentType = computed(() => documentDetails.value ? documentDetails.value.type : "json");
		/** Check if the form should be disabled (when input is empty) */
		const isDisabled = computed(() => {
			return !inputContent.value.trim();
		});
		/**
		* Toggle watchMode based on whether the input is a local URL.
		* Only enables watch mode for local URLs, not for files or pasted content.
		*/
		watch(isLocalUrlInput, (value) => {
			watchMode.value = value;
		});
		/**
		* Handles errors during the import process.
		* Shows an error toast, invalidates the loader to show an error state,
		* and closes the command palette modal.
		*
		* @param errorMessage - The error message to display and log
		*/
		const handleImportError = async (errorMessage) => {
			console.error(errorMessage);
			toast(errorMessage, "error");
			await loader.invalidate();
			emit("close");
		};
		/**
		* Directly imports a document into the workspace without showing the modal.
		* This is used when there is only one workspace and it is empty.
		*/
		const handleImport = async (newSource, type) => {
			loader.start();
			const TEMP_DOCUMENT_NAME = "drafts";
			const draftStore = createWorkspaceStore({
				fileLoader: __props.fileLoader,
				meta: { "x-scalar-active-proxy": __props.workspaceStore.workspace["x-scalar-active-proxy"] }
			});
			if (!await loadDocumentFromSource(draftStore, {
				source: newSource,
				type: (() => {
					if (type) return type;
					if (isUrlInput.value) return "url";
					return "raw";
				})()
			}, TEMP_DOCUMENT_NAME, watchMode.value)) return handleImportError("Failed to import document");
			const importResult = await importDocumentToWorkspace({
				workspaceStore: __props.workspaceStore,
				workspaceState: draftStore.exportWorkspace(),
				name: TEMP_DOCUMENT_NAME
			});
			if (!importResult.ok) return handleImportError(importResult.error);
			await loader.validate();
			navigateToDocument(importResult.slug);
			emit("close");
		};
		/** Navigate to the document overview page after successful import */
		const navigateToDocument = (documentName) => {
			router.push({
				name: "document.overview",
				params: { documentSlug: documentName }
			});
		};
		/**
		* Handle file selection and import from file dialog.
		* Reads the file as text and imports it as OpenAPI or Postman collection.
		* Shows loading state during the import process.
		*/
		const { open: openSpecFileDialog } = useFileDialog({
			onChange: (files) => {
				const [file] = files ?? [];
				if (!file) return;
				loader.start();
				const onLoad = async (event) => {
					const text = event.target?.result;
					await handleImport(text, "raw");
				};
				const reader = new FileReader();
				reader.onload = onLoad;
				reader.readAsText(file);
			},
			multiple: false,
			accept: ".json,.yaml,.yml"
		});
		/**
		* Handle input changes.
		* Detects cURL commands and redirects to the cURL import command.
		*/
		const handleInput = (value) => {
			/** Redirect to cURL import command if input starts with 'curl' */
			if (value.trim().toLowerCase().startsWith("curl")) return __props.eventBus.emit("ui:open:command-palette", {
				action: "import-curl-command",
				payload: { inputValue: value }
			});
			inputContent.value = value;
		};
		/** Handle back navigation when user presses backspace on empty input */
		const handleBack = (event) => {
			emit("back", event);
		};
		return (_ctx, _cache) => {
			return openBlock(), createBlock(CommandActionForm_default, {
				disabled: isDisabled.value,
				loader: unref(loader),
				onSubmit: _cache[2] || (_cache[2] = ($event) => handleImport(inputContent.value))
			}, {
				options: withCtx(() => [createElementVNode("div", _hoisted_2, [renderSlot(_ctx.$slots, "fileUpload", { import: handleImport }, () => [createVNode(unref(ScalarButton), {
					class: "hover:bg-b-2 relative max-h-8 gap-1.5 p-2 text-xs",
					variant: "outlined",
					onClick: unref(openSpecFileDialog)
				}, {
					default: withCtx(() => [_cache[5] || (_cache[5] = createTextVNode(" JSON, or YAML File ", -1)), createVNode(unref(ScalarIcon), {
						class: "text-c-3",
						icon: "Upload",
						size: "md"
					})]),
					_: 1
				}, 8, ["onClick"])]), createVNode(unref(ScalarTooltip), {
					content: isUrlInput.value ? "Watch mode automatically updates the API client when the OpenAPI URL content changes, ensuring your client remains up-to-date." : "Watch mode is only available for URL imports. When enabled it automatically updates the API client when the OpenAPI URL content changes.",
					placement: "bottom"
				}, {
					default: withCtx(() => [createVNode(WatchModeToggle_default, {
						modelValue: watchMode.value,
						"onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => watchMode.value = $event),
						disabled: !isUrlInput.value
					}, null, 8, ["modelValue", "disabled"])]),
					_: 1
				}, 8, ["content"])])]),
				submit: withCtx(() => [_cache[6] || (_cache[6] = createTextVNode(" Import ", -1)), isUrlInput.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode("from URL")], 64)) : documentDetails.value && documentType.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [documentDetails.value.title ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(" \"" + toDisplayString(documentDetails.value.title) + "\" ", 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(documentDetails.value.version), 1)], 64))], 64)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [createTextVNode("Collection")], 64))]),
				default: withCtx(() => [!documentDetails.value || isUrlInput.value ? (openBlock(), createBlock(CommandActionInput_default, {
					key: 0,
					modelValue: inputContent.value,
					placeholder: "OpenAPI/Swagger/Postman URL or cURL",
					onDelete: handleBack,
					"onUpdate:modelValue": handleInput
				}, null, 8, ["modelValue"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createElementVNode("div", _hoisted_1, [_cache[4] || (_cache[4] = createElementVNode("div", { class: "text-c-2 min-h-8 w-full py-2 pl-12 text-center text-xs" }, " Preview ", -1)), createVNode(unref(ScalarButton), {
					class: "hover:bg-b-2 relative ml-auto max-h-8 gap-1.5 p-2 text-xs",
					variant: "ghost",
					onClick: _cache[0] || (_cache[0] = ($event) => inputContent.value = "")
				}, {
					default: withCtx(() => [..._cache[3] || (_cache[3] = [createTextVNode(" Clear ", -1)])]),
					_: 1
				})]), documentDetails.value && !isUrlInput.value ? (openBlock(), createBlock(unref(ScalarCodeBlock), {
					key: 0,
					class: "bg-b-2 mt-1 max-h-[40dvh] rounded border px-2 py-1 text-sm",
					content: inputContent.value,
					copy: false,
					lang: documentType.value
				}, null, 8, ["content", "lang"])) : createCommentVNode("", true)], 64))]),
				_: 3
			}, 8, ["disabled", "loader"]);
		};
	}
});
//#endregion
export { CommandActionInput_default as n, CommandActionForm_default as r, CommandPaletteImport_default as t };

//# sourceMappingURL=CommandPaletteImport-Diu5dDIY.js.map