import { isHttpMethod, schemaModel } from "@scalar/oas-utils/helpers";
import { collectionSchema, createExampleFromRequest, requestSchema, securitySchemeSchema, serverSchema, tagSchema } from "@scalar/oas-utils/entities/spec";
import { z } from "zod";
import { getNestedValue } from "@scalar/object-utils/nested";
import microdiff from "microdiff";
//#region src/views/Request/libs/watch-mode.ts
/**
* Combine Rename Diffs
* Rename diffs show up as a delete and an add.
* This will go through the diff and combine any diff items which are next to each other which go from remove to add.
*
* - first we check if the payloads are the same then it was just a simple rename
* - next we will add the rename and also handle any changes in the diff
*/
var combineRenameDiffs = (diff, pathPrefix = []) => {
	const combined = [];
	let skipNext = false;
	for (let i = 0; i < diff.length; i++) {
		if (skipNext) {
			skipNext = false;
			continue;
		}
		const current = diff[i];
		const next = diff[i + 1];
		if (!current) continue;
		if (pathPrefix.length) {
			current.path = [...pathPrefix, ...current.path];
			if (next) next.path = [...pathPrefix, ...next.path];
		} else if (current.path[0] !== "paths") {
			combined.push(current);
			continue;
		}
		if (current.type === "REMOVE" && next?.type === "CREATE") {
			const [, currPath, currMethod] = current.path;
			const [, nextPath, nextMethod] = next.path;
			const nestedPrefix = ["paths", nextPath].filter((p) => typeof p === "string");
			if (currPath !== nextPath) combined.push({
				type: "CHANGE",
				path: ["paths", "path"],
				oldValue: currPath,
				value: nextPath
			});
			if (currMethod && typeof nextMethod === "string" && currMethod !== nextMethod && nextPath) {
				combined.push({
					type: "CHANGE",
					path: [
						"paths",
						nextPath,
						"method"
					],
					oldValue: currMethod,
					value: nextMethod
				});
				nestedPrefix.push(nextMethod);
			}
			if (pathPrefix.length === 0) {
				const innerDiff = microdiff(current.oldValue, next.value);
				if (innerDiff.length) {
					const innerCombined = combineRenameDiffs(innerDiff, nestedPrefix);
					combined.push(...innerCombined);
				}
			}
			skipNext = true;
		} else if (current.type === "CREATE" && current.path.length > 3 && typeof current.path.at(-1) !== "number") combined.push({
			...current,
			type: "CHANGE",
			oldValue: void 0
		});
		else if (current.type === "REMOVE" && current.path.length > 3 && typeof current.path.at(-1) !== "number") combined.push({
			...current,
			type: "CHANGE",
			value: void 0
		});
		else combined.push(current);
	}
	return combined;
};
/** Like array.find but returns the resource instead of the uid */
var findResource = (arr, resources, condition) => {
	for (const uid of arr) {
		const resource = resources[uid];
		if (resource && condition(resource)) return resource;
	}
	return null;
};
/** Helper function to unwrap optional and default schemas */
var unwrapSchema = (schema) => {
	if (schema instanceof z.ZodOptional) {
		const unwrapped = schema.unwrap();
		if (unwrapped instanceof z.ZodType) return unwrapSchema(unwrapped);
	}
	if (schema instanceof z.ZodDefault) {
		const innerType = schema._zod.def.innerType;
		if (innerType instanceof z.ZodType) return unwrapSchema(innerType);
	}
	if (schema instanceof z.ZodCatch) {
		const innerType = schema._zod.def.innerType;
		if (innerType instanceof z.ZodType) return unwrapSchema(innerType);
	}
	if (schema instanceof z.ZodPipe) {
		const inType = schema._zod.def.in;
		if (inType instanceof z.ZodType) return unwrapSchema(inType);
	}
	return schema;
};
/**
* Traverses a zod schema based on the path and returns the schema at the end of the path
* or null if the path doesn't exist. Handles optional unwrapping, records, and arrays
*/
var traverseZodSchema = (schema, path) => {
	let currentSchema = schema;
	for (const key of path) {
		currentSchema = unwrapSchema(currentSchema);
		if (currentSchema instanceof z.ZodAny) return currentSchema;
		if (currentSchema instanceof z.ZodObject && typeof key === "string" && key in currentSchema.shape) {
			const shapeValue = currentSchema.shape[key];
			if (shapeValue instanceof z.ZodType) currentSchema = shapeValue;
			else return null;
		} else if (currentSchema instanceof z.ZodArray) if (typeof key === "number") if (currentSchema.element instanceof z.ZodType) currentSchema = currentSchema.element;
		else return null;
		else if (typeof key === "string") {
			if (currentSchema.element instanceof z.ZodType) currentSchema = currentSchema.element;
			else return null;
			if (currentSchema instanceof z.ZodObject && key in currentSchema.shape) {
				const shapeValue = currentSchema.shape[key];
				if (shapeValue instanceof z.ZodType) currentSchema = shapeValue;
				else return null;
			} else return null;
		} else return null;
		else if (currentSchema instanceof z.ZodRecord) {
			const valueSchema = currentSchema._zod.def.valueType;
			if (!valueSchema || !(valueSchema instanceof z.ZodType)) return null;
			if (typeof key === "string") {
				currentSchema = valueSchema;
				currentSchema = unwrapSchema(currentSchema);
			} else if (typeof key === "number") {
				currentSchema = valueSchema;
				currentSchema = unwrapSchema(currentSchema);
				if (currentSchema instanceof z.ZodArray) if (currentSchema.element instanceof z.ZodType) currentSchema = currentSchema.element;
				else return null;
			} else currentSchema = valueSchema;
		} else return null;
		currentSchema = unwrapSchema(currentSchema);
	}
	return currentSchema;
};
/**
* Takes in diff, uses the path to get to the nested schema then parse the value
* If there is a sub schema and it successfully parses, both the path and new value are valid and returned as such
*
* We return a tuple to make it easier to pass into the mutators
*/
var parseDiff = (schema, diff) => {
	const parsedSchema = traverseZodSchema(schema, diff.path);
	if (!parsedSchema) return null;
	const path = diff.path.join(".");
	const pathMinusOne = diff.path.slice(0, -1).join(".");
	if (diff.type === "REMOVE") return {
		path,
		pathMinusOne,
		value: void 0
	};
	const parsedValue = schemaModel(diff.value, parsedSchema, false);
	if (typeof parsedValue === "undefined" || parsedValue === null) return null;
	return {
		path,
		pathMinusOne,
		value: parsedValue
	};
};
/**
* Transforms the diff into a payload for the collection mutator then executes that mutation
*
* @returns true if it succeeds, and false for a failure
*/
var mutateCollectionDiff = (diff, { activeCollection }, { collectionMutators }) => {
	if (!activeCollection.value) return false;
	if (typeof diff.path[diff.path.length - 1] === "number" && (diff.type === "CREATE" || diff.type === "REMOVE")) {
		const parsed = parseDiff(collectionSchema, {
			...diff,
			path: diff.path
		});
		if (!parsed) return false;
		const oldValue = [...getNestedValue(activeCollection.value, parsed.pathMinusOne)];
		if (diff.type === "CREATE") oldValue.push(parsed.value);
		else if (diff.type === "REMOVE") oldValue.pop();
		collectionMutators.edit(activeCollection.value.uid, parsed.pathMinusOne, oldValue);
	} else {
		const parsed = parseDiff(collectionSchema, diff);
		if (!parsed) return false;
		collectionMutators.edit(activeCollection.value.uid, parsed.path, parsed.value);
	}
	return true;
};
/**
* Currently we just generate new examples
*
* TODO: diff the changes in the examples and just update what we need to
*/
var updateRequestExamples = (requestUid, store) => {
	const { requests, requestExamples, requestExampleMutators } = store;
	const request = requests[requestUid];
	request?.examples.forEach((exampleUid) => {
		const newExample = createExampleFromRequest(request, requestExamples[exampleUid]?.name ?? "Default");
		if (newExample) requestExampleMutators.set({
			...newExample,
			uid: exampleUid
		});
	});
};
/**
* Generates an array of payloads for the request mutator from the request diff, also executes the mutation
*/
var mutateRequestDiff = (diff, { activeCollection }, store) => {
	if (!activeCollection.value) return false;
	const { requests, requestMutators } = store;
	const [, path, method, ...keys] = diff.path;
	if (path === "path" && diff.type === "CHANGE") activeCollection.value.requests.forEach((uid) => {
		if (requests[uid]?.path === diff.oldValue) requestMutators.edit(uid, "path", diff.value);
	});
	else if (method === "method" && diff.type === "CHANGE") activeCollection.value.requests.forEach((uid) => {
		if (requests[uid]?.method === diff.oldValue && requests[uid]?.path === path) requestMutators.edit(uid, "method", diff.value);
	});
	else if (diff.type !== "CHANGE" && typeof keys.at(-1) === "number") {
		const request = findResource(activeCollection.value.requests, requests, (r) => r.path === path && r.method === method);
		const parsed = parseDiff(requestSchema, {
			...diff,
			path: diff.path.slice(3)
		});
		if (!request || !parsed) return false;
		const oldValue = [...getNestedValue(request, parsed.pathMinusOne)];
		if (diff.type === "CREATE") oldValue.push(parsed.value);
		else if (diff.type === "REMOVE") oldValue.pop();
		requestMutators.edit(request.uid, parsed.pathMinusOne, oldValue);
		if (diff.path[3] === "parameters" || diff.path[3] === "requestBody") updateRequestExamples(request.uid, store);
	} else if (diff.type === "CREATE") {
		const [firstEntry] = Object.entries(diff.value ?? {});
		const [_method, _operation] = firstEntry ?? [];
		const operation = method ? diff.value : _operation;
		const newMethod = method || _method;
		const operationServers = serverSchema.array().parse(operation.servers ?? []);
		const { security: operationSecurity, ...operationWithoutSecurity } = operation;
		const requestPayload = {
			...operationWithoutSecurity,
			method: isHttpMethod(newMethod) ? newMethod : "get",
			path,
			parameters: operation.parameters ?? [],
			servers: operationServers.map((s) => s.uid)
		};
		if (operationSecurity?.length) requestPayload.security = operationSecurity.map((s) => {
			if (Object.keys(s).length) {
				const [key] = Object.keys(s);
				if (!key) return s;
				return { [key]: s[key] };
			}
			return s;
		});
		const request = schemaModel(requestPayload, requestSchema, false);
		if (!request) return false;
		requestMutators.add(request, activeCollection.value.uid);
	} else if (diff.type === "REMOVE") {
		const request = findResource(activeCollection.value.requests, requests, (_request) => _request.path === path && _request.method === method);
		if (!request) return false;
		requestMutators.delete(request, activeCollection.value.uid);
	} else if (diff.type === "CHANGE") {
		const request = findResource(activeCollection.value.requests, requests, (r) => r.path === path && r.method === method);
		const parsed = parseDiff(requestSchema, {
			...diff,
			path: keys
		});
		if (!request || !parsed) return false;
		requestMutators.edit(request.uid, parsed.path, parsed.value);
		if (diff.path[3] === "parameters" || diff.path[3] === "requestBody") updateRequestExamples(request.uid, store);
	}
	return true;
};
/** Generates a payload for the server mutator from the server diff including the mutator method */
var mutateServerDiff = (diff, { activeCollection }, { servers, serverMutators }) => {
	if (!activeCollection.value) return false;
	const [, index, ...keys] = diff.path;
	if (keys?.length) {
		const serverUid = activeCollection.value.servers[index];
		if (!serverUid) return false;
		const server = servers[serverUid];
		const parsed = parseDiff(serverSchema, {
			...diff,
			path: keys
		});
		if (!server || !parsed) return false;
		const value = diff.type === "REMOVE" && keys[keys.length - 1] === "variables" ? {} : parsed.value;
		serverMutators.edit(serverUid, parsed.path, value);
	} else if (diff.type === "REMOVE") {
		if (!activeCollection.value.servers[index]) return false;
		serverMutators.delete(activeCollection.value.servers[index], activeCollection.value.uid);
	} else if (diff.type === "CREATE") {
		const parsed = schemaModel(diff.value, serverSchema, false);
		if (!parsed) return false;
		serverMutators.add(parsed, activeCollection.value.uid);
	}
	return true;
};
/** Generates a payload for the tag mutator from the tag diff */
var mutateTagDiff = (diff, { activeCollection }, { tags, tagMutators }) => {
	if (!activeCollection.value) return false;
	const [, index, ...keys] = diff.path;
	if (keys?.length) {
		const tagUid = activeCollection.value.tags[index];
		if (!tagUid) return false;
		const tag = tags[tagUid];
		const parsed = parseDiff(tagSchema, {
			...diff,
			path: keys
		});
		if (!tag || !parsed) return false;
		tagMutators.edit(tagUid, parsed.path, parsed.value);
	} else if (diff.type === "REMOVE") {
		const tagUid = activeCollection.value.tags[index];
		if (!tagUid) return false;
		const tag = tags[tagUid];
		if (!tag) return false;
		tagMutators.delete(tag, activeCollection.value.uid);
	} else if (diff.type === "CREATE") {
		const parsed = schemaModel(diff.value, tagSchema, false);
		if (!parsed) return false;
		tagMutators.add(parsed, activeCollection.value.uid);
	}
	return true;
};
/** Narrows down a zod union schema */
var narrowUnionSchema = (schema, key, value) => {
	const _schema = unwrapSchema(schema);
	if (_schema instanceof z.ZodUnion || _schema instanceof z.ZodDiscriminatedUnion) for (const option of _schema.options) {
		if (!(option instanceof z.ZodType)) continue;
		const unwrappedOption = unwrapSchema(option);
		if (unwrappedOption instanceof z.ZodObject && key in unwrappedOption.shape) {
			const fieldSchema = unwrapSchema(unwrappedOption.shape[key]);
			if (fieldSchema instanceof z.ZodLiteral && fieldSchema.value === value) return unwrappedOption;
			if (fieldSchema instanceof z.ZodDefault && fieldSchema._zod.def.innerType instanceof z.ZodLiteral && fieldSchema._zod.def.innerType.value === value) return unwrappedOption;
			if (fieldSchema instanceof z.ZodOptional && fieldSchema._zod.def.innerType instanceof z.ZodLiteral && fieldSchema._zod.def.innerType.value === value) return unwrappedOption;
			if (fieldSchema instanceof z.ZodDefault && fieldSchema._zod.def.innerType instanceof z.ZodOptional && fieldSchema._zod.def.innerType._zod.def.innerType instanceof z.ZodLiteral && fieldSchema._zod.def.innerType._zod.def.innerType.value === value) return unwrappedOption;
		}
	}
	return null;
};
/**
* Generates a payload for the security scheme mutator from the security scheme diff, then executes that mutation
*
* Note: for edit we cannot use parseDiff here as it can't do unions, so we handle the unions first
*
* @returns true if it succeeds, and false for a failure
*/
var mutateSecuritySchemeDiff = (diff, { activeCollection }, { securitySchemes, securitySchemeMutators }) => {
	if (!activeCollection.value) return false;
	const [, , schemeName, ...keys] = diff.path;
	const scheme = securitySchemes[schemeName] ?? findResource(activeCollection.value.securitySchemes, securitySchemes, (s) => s.nameKey === schemeName);
	if (keys?.length) {
		const schema = narrowUnionSchema(securitySchemeSchema, "type", scheme?.type ?? "");
		if (!schema || !scheme) return false;
		const parsed = parseDiff(schema, {
			...diff,
			path: keys
		});
		if (!parsed) return false;
		const path = parsed.path;
		securitySchemeMutators.edit(scheme.uid, path, parsed.value);
	} else if (diff.type === "REMOVE") {
		if (!scheme) return false;
		securitySchemeMutators.delete(scheme.uid);
	} else if (diff.type === "CREATE") securitySchemeMutators.add(securitySchemeSchema.parse(diff.value), activeCollection.value.uid);
	return true;
};
//#endregion
export { combineRenameDiffs, findResource, mutateCollectionDiff, mutateRequestDiff, mutateSecuritySchemeDiff, mutateServerDiff, mutateTagDiff, narrowUnionSchema, parseDiff, traverseZodSchema };

//# sourceMappingURL=watch-mode.js.map