//#region src/utils/json-schema-walk.ts
const DEFAULT_MAX_DEPTH = 12;
function resolveRef(node, ctx) {
	if (node && typeof node === "object" && typeof node.$ref === "string") {
		const ref = node.$ref;
		if (ref === "#") return {
			node: ctx.root ?? node,
			refName: "#"
		};
		const refName = ref.split("/").pop();
		const resolved = ctx.defs[refName];
		return resolved ? {
			node: resolved,
			refName
		} : {
			node,
			refName
		};
	}
	return { node };
}
function unwrapNullable(node) {
	if (!node || typeof node !== "object") return {
		inner: node,
		nullable: false
	};
	if (Array.isArray(node.type) && node.type.includes("null")) {
		const nonNull = node.type.filter((t) => t !== "null");
		return {
			inner: {
				...node,
				type: nonNull.length === 1 ? nonNull[0] : nonNull
			},
			nullable: true
		};
	}
	const variants = node.anyOf ?? node.oneOf;
	if (Array.isArray(variants)) {
		const nonNull = variants.filter((v) => v?.type !== "null");
		const hasNull = variants.some((v) => v?.type === "null");
		if (hasNull && nonNull.length === 1) return {
			inner: nonNull[0],
			nullable: true
		};
		if (!hasNull && nonNull.length === 1) return {
			inner: nonNull[0],
			nullable: false
		};
		if (hasNull) {
			const key = node.anyOf ? "anyOf" : "oneOf";
			return {
				inner: {
					...node,
					[key]: nonNull
				},
				nullable: true
			};
		}
	}
	return {
		inner: node,
		nullable: false
	};
}
/**
* Flatten an `allOf` composition into a single node by merging the (deref'd) branches'
* properties, required, type, description, and enum. Pydantic v2 emits this shape when it
* combines a `$ref` with extra constraints (e.g. `{ allOf: [{ $ref }], description }`).
*/
function mergeAllOf(node, ctx) {
	if (!node || typeof node !== "object" || !Array.isArray(node.allOf)) return node;
	const merged = { ...node };
	merged.allOf = void 0;
	merged.properties = { ...node.properties ?? {} };
	const required = new Set(Array.isArray(node.required) ? node.required : []);
	const stack = [...node.allOf];
	const visited = /* @__PURE__ */ new Set();
	while (stack.length > 0) {
		const { node: branch, refName } = resolveRef(stack.pop(), ctx);
		if (refName) {
			if (visited.has(refName)) continue;
			visited.add(refName);
		}
		if (!branch || typeof branch !== "object") continue;
		if (Array.isArray(branch.allOf)) stack.push(...branch.allOf);
		if (branch.properties && typeof branch.properties === "object") Object.assign(merged.properties, branch.properties);
		if (Array.isArray(branch.required)) for (const r of branch.required) required.add(r);
		if (!merged.type && typeof branch.type === "string") merged.type = branch.type;
		if (!merged.description && typeof branch.description === "string") merged.description = branch.description;
		if (!merged.enum && Array.isArray(branch.enum)) merged.enum = branch.enum;
	}
	if (Object.keys(merged.properties).length === 0) merged.properties = void 0;
	if (required.size > 0) merged.required = [...required];
	if (!merged.type && merged.properties) merged.type = "object";
	return merged;
}
function nodeType(node) {
	if (!node) return "string";
	if (typeof node.type === "string") return node.type;
	if (Array.isArray(node.type)) return node.type.filter((t) => t !== "null")[0] || "string";
	if (Array.isArray(node.anyOf) || Array.isArray(node.oneOf)) return "union";
	if (Array.isArray(node.enum)) return typeof node.enum[0] === "number" ? "number" : "string";
	return "string";
}
function enumValues(node) {
	if (Array.isArray(node?.enum)) return node.enum;
	if (node?.const !== void 0) return [node.const];
}
function walkNode(name, rawNode, required, ctx) {
	const { node: derefed, refName: outerRef } = resolveRef(rawNode, ctx);
	const { inner: unwrapped, nullable } = unwrapNullable(derefed);
	const { node: reResolved, refName: innerRef } = resolveRef(unwrapped, ctx);
	const inner = mergeAllOf(reResolved, ctx);
	const refName = outerRef ?? innerRef;
	const descRaw = inner?.description ?? rawNode?.description;
	const description = typeof descRaw === "string" ? descRaw.trim() || void 0 : void 0;
	const type = nodeType(inner);
	const base = {
		name,
		type,
		required,
		nullable,
		description
	};
	if ((refName ? ctx.seen.has(refName) : false) || ctx.depth >= ctx.maxDepth) return {
		...base,
		recursive: true
	};
	const childCtx = refName ? {
		...ctx,
		depth: ctx.depth + 1,
		seen: new Set([...ctx.seen, refName])
	} : {
		...ctx,
		depth: ctx.depth + 1
	};
	if (inner?.properties && typeof inner.properties === "object") {
		const entries = Object.entries(inner.properties);
		if (entries.length > 0) {
			const childRequired = Array.isArray(inner.required) ? inner.required : [];
			const properties = entries.map(([k, v]) => walkNode(k, v, childRequired.includes(k), childCtx));
			return {
				...base,
				type: "object",
				properties
			};
		}
	}
	if (type === "array" && inner?.items && typeof inner.items === "object") return {
		...base,
		type: "array",
		items: walkNode("item", inner.items, false, childCtx)
	};
	const branches = inner?.anyOf ?? inner?.oneOf;
	if (Array.isArray(branches) && branches.length > 1) {
		const variants = branches.map((branch, i) => walkNode(`variant${i}`, branch, false, childCtx));
		return {
			...base,
			type: "union",
			variants
		};
	}
	const ev = enumValues(inner);
	return ev ? {
		...base,
		enumValues: ev
	} : base;
}
/**
* Normalize a schema's top-level `properties` into ref-resolved, cycle-safe nodes.
* `$defs`/`definitions` and `$ref: "#"` are resolved against the passed schema root.
*/
function normalizeJsonSchemaProperties(schema, options = {}) {
	if (!schema || typeof schema !== "object") return [];
	const properties = schema.properties ?? {};
	const required = Array.isArray(schema.required) ? schema.required : [];
	const ctx = {
		defs: schema.$defs ?? schema.definitions ?? {},
		root: schema,
		depth: 0,
		maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH,
		seen: /* @__PURE__ */ new Set()
	};
	return Object.entries(properties).map(([name, node]) => walkNode(name, node, required.includes(name), ctx));
}
/**
* Unwrap an AI SDK `jsonSchema()` wrapper (`{ jsonSchema, validate, _type }`) to the
* underlying JSON Schema. The MCP ingestion fallback wraps a raw schema this way; without
* unwrapping, downstream readers (provider tool def, prompt renderer, manage UI) see the
* wrapper's top-level keys instead of the real `properties`/`$defs`. A non-wrapper value
* (plain JSON Schema, or a Zod schema, which has no `jsonSchema` property) passes through.
*/
function unwrapJsonSchemaWrapper(value) {
	if (value && typeof value === "object") {
		const inner = value.jsonSchema;
		if (inner && typeof inner === "object") return inner;
	}
	return value;
}

//#endregion
export { normalizeJsonSchemaProperties, unwrapJsonSchemaWrapper };