import { getLogger } from "./logger.js";
import { z } from "@hono/zod-openapi";

//#region src/utils/schema-conversion.ts
const logger = getLogger("schema-conversion");
/**
* Utility function for converting Zod schemas to JSON Schema
* Uses Zod's built-in toJSONSchema method
*/
function convertZodToJsonSchema(zodSchema) {
	try {
		const jsonSchema = z.toJSONSchema(zodSchema);
		if (jsonSchema.$schema) delete jsonSchema.$schema;
		return jsonSchema;
	} catch (error) {
		logger.error({
			error: error instanceof Error ? error.message : "Unknown error",
			stack: error instanceof Error ? error.stack : void 0
		}, "Failed to convert Zod schema to JSON Schema");
		throw new Error("Failed to convert Zod schema to JSON Schema");
	}
}
/**
* Simple helper to mark a Zod schema field as a preview field
* Adds metadata to the schema definition without modifying Zod's core
*/
const preview = (schema) => {
	schema._def.inPreview = true;
	return schema;
};
/**
* Convert Zod schema to JSON Schema while preserving preview metadata
*/
function convertZodToJsonSchemaWithPreview(zodSchema) {
	const jsonSchema = convertZodToJsonSchema(zodSchema);
	if (zodSchema instanceof z.ZodObject && jsonSchema.properties) {
		const shape = zodSchema.shape;
		for (const [key, fieldSchema] of Object.entries(shape)) if (fieldSchema?._def?.inPreview === true) jsonSchema.properties[key].inPreview = true;
	}
	return jsonSchema;
}
/**
* Type guard to check if a value is a Zod schema
*/
function isZodSchema(value) {
	return value?._def?.type === "object";
}
/**
* Strips JSON Schema numeric constraints that are not supported by all LLM providers.
*
* Anthropic structured output rejects `minimum`, `maximum`, `exclusiveMinimum`,
* `exclusiveMaximum`, and `multipleOf` on `number`/`integer` types.
* Applied recursively to handle nested objects and arrays.
*/
function stripUnsupportedConstraints(schema) {
	if (!schema || typeof schema !== "object") return schema;
	const stripped = { ...schema };
	if (stripped.type === "number" || stripped.type === "integer") {
		delete stripped.minimum;
		delete stripped.maximum;
		delete stripped.exclusiveMinimum;
		delete stripped.exclusiveMaximum;
		delete stripped.multipleOf;
	}
	if (stripped.properties && typeof stripped.properties === "object") {
		const strippedProperties = {};
		for (const [key, value] of Object.entries(stripped.properties)) strippedProperties[key] = stripUnsupportedConstraints(value);
		stripped.properties = strippedProperties;
	}
	if (stripped.items) stripped.items = stripUnsupportedConstraints(stripped.items);
	if (Array.isArray(stripped.anyOf)) stripped.anyOf = stripped.anyOf.map((s) => stripUnsupportedConstraints(s));
	if (Array.isArray(stripped.oneOf)) stripped.oneOf = stripped.oneOf.map((s) => stripUnsupportedConstraints(s));
	if (Array.isArray(stripped.allOf)) stripped.allOf = stripped.allOf.map((s) => stripUnsupportedConstraints(s));
	return stripped;
}
/**
* Makes all properties required in an object schema, wrapping originally-optional
* fields as `{ anyOf: [<schema>, { type: 'null' }] }`.
*
* OpenAI strict-mode structured output requires every key in `properties` to also
* appear in `required`. Applied recursively to handle nested objects and arrays.
*/
function makeAllPropertiesRequired(schema) {
	if (!schema || typeof schema !== "object") return schema;
	const normalized = { ...schema };
	if (normalized.properties && typeof normalized.properties === "object") {
		const originalRequired = Array.isArray(normalized.required) ? normalized.required : [];
		normalized.required = Object.keys(normalized.properties);
		const normalizedProperties = {};
		for (const [key, value] of Object.entries(normalized.properties)) {
			const processed = makeAllPropertiesRequired(value);
			const alreadyNullable = Array.isArray(processed.anyOf) && processed.anyOf.some((s) => s?.type === "null") || processed.nullable === true;
			normalizedProperties[key] = originalRequired.includes(key) || alreadyNullable ? processed : { anyOf: [processed, { type: "null" }] };
		}
		normalized.properties = normalizedProperties;
	}
	if (normalized.items) normalized.items = makeAllPropertiesRequired(normalized.items);
	if (Array.isArray(normalized.anyOf)) normalized.anyOf = normalized.anyOf.map((s) => makeAllPropertiesRequired(s));
	if (Array.isArray(normalized.oneOf)) normalized.oneOf = normalized.oneOf.map((s) => makeAllPropertiesRequired(s));
	if (Array.isArray(normalized.allOf)) normalized.allOf = normalized.allOf.map((s) => makeAllPropertiesRequired(s));
	return normalized;
}
/**
* Normalizes a data component JSON Schema for cross-provider LLM compatibility.
*
* Applies two transformations in order:
* 1. `stripUnsupportedConstraints` — removes `minimum`/`maximum`/etc. from numbers
*    (Anthropic structured output rejects these)
* 2. `makeAllPropertiesRequired` — ensures every property appears in `required`,
*    wrapping optional fields as nullable (OpenAI strict-mode requires this)
*/
function normalizeDataComponentSchema(schema) {
	return makeAllPropertiesRequired(stripUnsupportedConstraints(schema));
}
/**
* Extract preview fields from either JSON Schema or Zod schema
*/
function extractPreviewFields(schema) {
	const previewFields = [];
	if (schema instanceof z.ZodObject) {
		const shape = schema.shape;
		for (const [key, fieldSchema] of Object.entries(shape)) if (fieldSchema?._def?.inPreview === true) previewFields.push(key);
		return previewFields;
	}
	if (schema?.type === "object" && schema.properties) {
		for (const [key, prop] of Object.entries(schema.properties)) if (prop.inPreview === true) previewFields.push(key);
	}
	return previewFields;
}

//#endregion
export { convertZodToJsonSchema, convertZodToJsonSchemaWithPreview, extractPreviewFields, isZodSchema, makeAllPropertiesRequired, normalizeDataComponentSchema, preview, stripUnsupportedConstraints };