import { buildToolModelConfigFromCandidates, coerceToolModelConfig, hasToolModelConfig } from "../image/tool-model-config.js";
import { applyImageGenerationModelConfigDefaults } from "../image/image-helpers.js";
import { isFailoverError } from "../failover-error.js";
import { imageAssetFromDataUrl, imageFileExtensionForMimeType, mimeTypeFromFileName, parseImageDataUrl, sniffImageMimeType } from "../image/generation/image-assets.js";
import { getImageGenerationProvider } from "../image/generation/provider-registry.js";
import { generateImage, listImageGenerationProvidersSummary } from "../image/generation/runtime.js";
import { randomBytes } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { Type } from "@sinclair/typebox";
//#region src/agent/tools/image-generate-tool.ts
const DEFAULT_COUNT = 1;
const MAX_COUNT = 9;
const ALLOWED_QUALITIES = [
	"low",
	"medium",
	"high",
	"auto"
];
const ALLOWED_OUTPUT_FORMATS = [
	"png",
	"jpeg",
	"webp"
];
const ALLOWED_BACKGROUNDS = [
	"transparent",
	"opaque",
	"auto"
];
const ALLOWED_RESOLUTIONS = [
	"1K",
	"2K",
	"4K"
];
const ImageGenerateToolSchema = Type.Object({
	action: Type.Optional(Type.String({ description: "Optional: \"generate\" (default) or \"list\" available image-generation providers." })),
	prompt: Type.Optional(Type.String({ description: "Image generation prompt." })),
	model: Type.Optional(Type.String({ description: "Optional provider/model override, e.g. openai/gpt-image-1 / dashscope/wan2.7-image-pro / minimax/image-01." })),
	filename: Type.Optional(Type.String({ description: "Optional basename hint for saved files." })),
	size: Type.Optional(Type.String({ description: "Optional pixel size, e.g. 1024x1024." })),
	aspectRatio: Type.Optional(Type.String({ description: "Optional aspect ratio, e.g. 16:9 / 9:16 / 1:1." })),
	resolution: Type.Optional(Type.String({ description: "Optional resolution tier: 1K / 2K / 4K (provider-dependent)." })),
	count: Type.Optional(Type.Number({
		minimum: 1,
		maximum: MAX_COUNT,
		description: `Number of images (1–${MAX_COUNT}).`
	})),
	quality: Type.Optional(Type.String({ description: "Optional quality: low / medium / high / auto." })),
	outputFormat: Type.Optional(Type.String({ description: "Optional output format: png / jpeg / webp." })),
	background: Type.Optional(Type.String({ description: "Optional background: transparent / opaque / auto." })),
	inputImages: Type.Optional(Type.Array(Type.Object({ source: Type.String({ description: "Workspace-relative file path or `data:image/...;base64,...` URL." }) }), { description: "Reference images for edit mode (provider must support edit)." })),
	providerOptions: Type.Optional(Type.Object({ openai: Type.Optional(Type.Object({
		moderation: Type.Optional(Type.String()),
		outputCompression: Type.Optional(Type.Number()),
		user: Type.Optional(Type.String())
	})) }))
});
function readStringParam(args, key) {
	const v = args[key];
	return typeof v === "string" && v.trim() ? v.trim() : void 0;
}
function readEnumParam(args, key, allowed) {
	const raw = readStringParam(args, key);
	if (!raw) return void 0;
	const lower = raw.toLowerCase();
	return allowed.includes(lower) ? lower : void 0;
}
function readResolutionParam(args) {
	const raw = readStringParam(args, "resolution");
	if (!raw) return void 0;
	const upper = raw.toUpperCase();
	return ALLOWED_RESOLUTIONS.includes(upper) ? upper : void 0;
}
function readProviderOptions(args) {
	const raw = args.providerOptions;
	if (!raw || typeof raw !== "object") return void 0;
	const out = {};
	const oa = raw.openai;
	if (oa && typeof oa === "object") {
		const oaRec = oa;
		const moderation = typeof oaRec.moderation === "string" ? oaRec.moderation.toLowerCase() : void 0;
		const outputCompression = typeof oaRec.outputCompression === "number" && Number.isFinite(oaRec.outputCompression) ? oaRec.outputCompression : void 0;
		const user = typeof oaRec.user === "string" && oaRec.user.trim() ? oaRec.user.trim() : void 0;
		out.openai = {
			...moderation === "low" || moderation === "auto" ? { moderation } : {},
			...outputCompression !== void 0 ? { outputCompression } : {},
			...user ? { user } : {}
		};
		if (Object.keys(out.openai).length === 0) delete out.openai;
	}
	return Object.keys(out).length > 0 ? out : void 0;
}
var ToolInputError = class extends Error {};
async function loadInputImages(params) {
	if (!params.sources?.length) return [];
	const out = [];
	const workspaceAbs = path.resolve(params.workspace);
	for (const entry of params.sources) {
		const source = entry?.source;
		if (typeof source !== "string" || !source.trim()) throw new ToolInputError("inputImages[].source is required.");
		const trimmed = source.trim();
		if (/^data:image\//i.test(trimmed)) {
			const parsed = imageAssetFromDataUrl(trimmed);
			if (!parsed) throw new ToolInputError("Invalid data URL in inputImages[].source.");
			out.push(parsed);
			continue;
		}
		if (/^https?:\/\//i.test(trimmed)) throw new ToolInputError("Sandboxed image_generate does not allow remote URLs.");
		const resolved = path.resolve(workspaceAbs, trimmed);
		if (resolved !== workspaceAbs && !resolved.startsWith(workspaceAbs + path.sep)) throw new ToolInputError("inputImages[].source escapes workspace.");
		let buffer;
		try {
			buffer = await readFile(resolved);
		} catch (e) {
			throw new ToolInputError(`inputImages[].source not readable: ${e instanceof Error ? e.message : String(e)}`);
		}
		const mimeType = mimeTypeFromFileName(resolved) ?? sniffImageMimeType(buffer).mimeType;
		out.push({
			buffer,
			mimeType,
			fileName: path.basename(resolved)
		});
	}
	return out;
}
function resolveImageGenerationModelConfigForTool(params) {
	const explicit = coerceToolModelConfig(params.cfg?.agents?.defaults?.imageGenerationModel);
	if (hasToolModelConfig(explicit)) return explicit;
	return buildToolModelConfigFromCandidates({
		explicit,
		candidates: listImageGenerationProvidersSummary(params.cfg).filter((p) => {
			const provider = getImageGenerationProvider(p.id, params.cfg);
			try {
				return provider?.isConfigured?.({ cfg: params.cfg }) ?? false;
			} catch {
				return false;
			}
		}).map((p) => `${p.id}/${p.defaultModel ?? p.models[0] ?? ""}`).filter((s) => /^[a-z0-9-]+\/.+/.test(s))
	});
}
async function saveGeneratedImages(params) {
	const dir = path.join(params.workspace, "media", "generated");
	await mkdir(dir, { recursive: true });
	const out = [];
	const random = randomBytes(4).toString("hex");
	let i = 0;
	for (const img of params.images) {
		i += 1;
		const ext = imageFileExtensionForMimeType(img.mimeType);
		const name = `${(params.filenameHint?.replace(/[^\w.-]/g, "") || "image") + `-${random}`}-${i}.${ext}`;
		const full = path.join(dir, name);
		await writeFile(full, img.buffer);
		out.push(full);
	}
	return out;
}
function createImageGenerateTool(options) {
	const imageGenerationModelConfig = resolveImageGenerationModelConfigForTool({ cfg: options.config });
	if (!imageGenerationModelConfig) return null;
	const effectiveCfg = applyImageGenerationModelConfigDefaults(options.config, imageGenerationModelConfig) ?? options.config;
	return {
		name: "image_generate",
		label: "Image Generation",
		description: "Generate images with the configured image-generation model (default OpenAI). Use action=\"list\" to see providers. Saves files under workspace/media/generated/.",
		parameters: ImageGenerateToolSchema,
		async execute(_toolCallId, args) {
			const params = args;
			const action = (readStringParam(params, "action") || "generate").toLowerCase();
			if (action === "list") {
				const providers = listImageGenerationProvidersSummary(effectiveCfg);
				return {
					content: [{
						type: "text",
						text: providers.flatMap((p) => [`${p.id}${p.defaultModel ? ` (default ${p.defaultModel})` : ""}`, `  models: ${p.models.join(", ")}`]).join("\n")
					}],
					details: { providers }
				};
			}
			if (action !== "generate") return {
				content: [{
					type: "text",
					text: "action must be \"generate\" or \"list\"."
				}],
				details: { error: "bad_action" }
			};
			const prompt = readStringParam(params, "prompt");
			if (!prompt) return {
				content: [{
					type: "text",
					text: "prompt is required for image generation."
				}],
				details: { error: "missing_prompt" }
			};
			const modelOverride = readStringParam(params, "model");
			const filename = readStringParam(params, "filename");
			const size = readStringParam(params, "size");
			const aspectRatio = readStringParam(params, "aspectRatio");
			const resolution = readResolutionParam(params);
			const quality = readEnumParam(params, "quality", ALLOWED_QUALITIES);
			const outputFormat = readEnumParam(params, "outputFormat", ALLOWED_OUTPUT_FORMATS);
			const background = readEnumParam(params, "background", ALLOWED_BACKGROUNDS);
			const providerOptions = readProviderOptions(params);
			const countRaw = params.count;
			const count = typeof countRaw === "number" && Number.isFinite(countRaw) ? Math.min(MAX_COUNT, Math.max(1, Math.floor(countRaw))) : DEFAULT_COUNT;
			let inputImages = [];
			try {
				inputImages = await loadInputImages({
					workspace: options.workspace,
					sources: params.inputImages ?? []
				});
			} catch (e) {
				return {
					content: [{
						type: "text",
						text: `Image generation failed: ${e instanceof Error ? e.message : String(e)}`
					}],
					details: { error: e instanceof ToolInputError ? "invalid_input_images" : "generation_failed" }
				};
			}
			try {
				const result = await generateImage({
					cfg: effectiveCfg,
					prompt,
					...modelOverride ? { modelOverride } : {},
					count,
					...size ? { size } : {},
					...aspectRatio ? { aspectRatio } : {},
					...resolution ? { resolution } : {},
					...quality ? { quality } : {},
					...outputFormat ? { outputFormat } : {},
					...background ? { background } : {},
					...inputImages.length > 0 ? { inputImages } : {},
					...providerOptions ? { providerOptions } : {}
				});
				const paths = await saveGeneratedImages({
					workspace: options.workspace,
					images: result.images,
					...filename ? { filenameHint: filename } : {}
				});
				const workspaceRelativePaths = paths.map((p) => path.relative(options.workspace, p).split(path.sep).join("/"));
				const lines = [`Generated ${paths.length} image(s) with ${result.provider}/${result.model}.`, ...paths.map((p) => `Saved: ${p}`)];
				for (const note of buildNormalizationNotes(result.normalization, result.ignoredOverrides)) lines.push(note);
				return {
					content: [{
						type: "text",
						text: lines.join("\n")
					}],
					details: {
						provider: result.provider,
						model: result.model,
						paths,
						workspaceRelativePaths,
						attempts: result.attempts,
						...result.normalization ? { normalization: result.normalization } : {},
						...result.ignoredOverrides.length > 0 ? { ignoredOverrides: result.ignoredOverrides } : {},
						...result.metadata ? { metadata: result.metadata } : {}
					}
				};
			} catch (e) {
				const msg = e instanceof Error ? e.message : String(e);
				const failoverFields = isFailoverError(e) ? {
					...e.reason !== void 0 ? { reason: e.reason } : {},
					...e.status !== void 0 ? { status: e.status } : {},
					...e.code !== void 0 ? { code: e.code } : {},
					...e.provider !== void 0 ? { provider: e.provider } : {},
					...e.model !== void 0 ? { model: e.model } : {},
					attempts: e.attempts
				} : {};
				return {
					content: [{
						type: "text",
						text: `Image generation failed: ${msg}`
					}],
					details: {
						error: "generation_failed",
						...failoverFields
					}
				};
			}
		}
	};
}
function buildNormalizationNotes(normalization, ignoredOverrides) {
	const notes = [];
	if (normalization) for (const [key, entry] of Object.entries(normalization)) {
		if (!entry) continue;
		const requested = entry.requested;
		const applied = entry.applied;
		if (requested !== void 0 && applied !== void 0 && requested !== applied) notes.push(`Note: requested ${key}="${requested}" → applied "${applied}".`);
		else if (applied !== void 0 && entry.derivedFrom) notes.push(`Note: ${key}="${applied}" derived from ${entry.derivedFrom}.`);
	}
	for (const ig of ignoredOverrides) notes.push(`Note: ignored ${ig.key}="${ig.value}" (not supported by provider).`);
	return notes;
}
//#endregion
export { createImageGenerateTool, parseImageDataUrl, resolveImageGenerationModelConfigForTool };

//# sourceMappingURL=image-generate-tool.js.map