import { fileTypeFromBuffer } from "file-type";
import sharp from "sharp";
import { Type } from "typebox";
import { z } from "zod";
import { createToolError } from "../utils/messages.js";
import { formatBytes } from "../utils/truncate.js";
import { formatZodError } from "../utils/zod.js";
import { TOOL_NAME_VIEW_IMAGE } from "./tool_names.js";
const VIEW_IMAGE_DESCRIPTION = [
    "View an image file and return it to the model.",
    "Only use this tool when the user explicitly requests viewing or analyzing an image.",
].join(" ");
const VIEW_IMAGE_PATH_DESCRIPTION = "Path to the image file to view.";
const VIEW_IMAGE_READ_MAX_BYTES = 50 * 1024 * 1024;
const VIEW_IMAGE_MODEL_MAX_BYTES = 2.5 * 1024 * 1024;
const VIEW_IMAGE_MAX_DIMENSION_PX = 2000;
const VIEW_IMAGE_DIMENSION_STEPS = [
    2000, 1920, 1792, 1664, 1536, 1408, 1280, 1152, 1024, 896, 768, 640, 512,
];
const VIEW_IMAGE_LOSSY_QUALITY_STEPS = [95, 90, 85, 80, 75];
const SUPPORTED_IMAGE_TYPES = ["image/jpeg", "image/png", "image/webp"];
export const VIEW_IMAGE_TOOL = {
    name: TOOL_NAME_VIEW_IMAGE,
    description: VIEW_IMAGE_DESCRIPTION,
    parameters: Type.Object({
        path: Type.String({ description: VIEW_IMAGE_PATH_DESCRIPTION }),
    }, { additionalProperties: false }),
};
const viewImageArgsSchema = z.object({
    path: z.string().trim().min(1),
});
function parseViewImageArgs(raw) {
    const parsed = viewImageArgsSchema.safeParse(raw);
    if (!parsed.success) {
        return { ok: false, error: formatZodError(parsed.error) };
    }
    return { ok: true, data: parsed.data };
}
function isSupportedImageType(mimeType) {
    return mimeType ? SUPPORTED_IMAGE_TYPES.includes(mimeType) : false;
}
function buildDimensionCaps(maxDimension) {
    const caps = new Set([maxDimension]);
    for (const step of VIEW_IMAGE_DIMENSION_STEPS) {
        if (step < maxDimension) {
            caps.add(step);
        }
    }
    return [...caps];
}
function buildLossyEncodePlans(mimeType) {
    return VIEW_IMAGE_LOSSY_QUALITY_STEPS.map((quality) => ({ mimeType, quality }));
}
function buildEncodePlans(args) {
    const { sourceMimeType, hasAlpha } = args;
    const plans = [];
    if (sourceMimeType === "image/png") {
        plans.push({ mimeType: "image/png" });
    }
    else {
        plans.push(...buildLossyEncodePlans(sourceMimeType));
    }
    if (sourceMimeType !== "image/webp") {
        plans.push({ mimeType: "image/webp", quality: 100, lossless: true });
        plans.push(...buildLossyEncodePlans("image/webp"));
    }
    if (!hasAlpha && sourceMimeType !== "image/jpeg") {
        plans.push(...buildLossyEncodePlans("image/jpeg"));
    }
    return plans;
}
async function encodeImageCandidate(args) {
    const { content, maxDimension, plan } = args;
    const pipeline = sharp(content).resize({
        width: maxDimension,
        height: maxDimension,
        fit: "inside",
        withoutEnlargement: true,
    });
    if (plan.mimeType === "image/jpeg") {
        return {
            mimeType: "image/jpeg",
            content: await pipeline.jpeg({ quality: plan.quality }).toBuffer(),
        };
    }
    if (plan.mimeType === "image/png") {
        return {
            mimeType: "image/png",
            content: await pipeline.png({ compressionLevel: 9 }).toBuffer(),
        };
    }
    return {
        mimeType: "image/webp",
        content: await pipeline
            .webp({
            quality: plan.quality,
            lossless: plan.lossless,
        })
            .toBuffer(),
    };
}
async function prepareImageForModel(content, sourceMimeType) {
    const metadata = await sharp(content).metadata();
    const width = metadata.width;
    const height = metadata.height;
    if (!width || !height) {
        throw new Error("Failed to read image dimensions.");
    }
    if (width <= VIEW_IMAGE_MAX_DIMENSION_PX &&
        height <= VIEW_IMAGE_MAX_DIMENSION_PX &&
        content.byteLength <= VIEW_IMAGE_MODEL_MAX_BYTES) {
        return { content, mimeType: sourceMimeType };
    }
    const maxDimension = Math.min(VIEW_IMAGE_MAX_DIMENSION_PX, Math.max(width, height));
    const dimensionCaps = buildDimensionCaps(maxDimension);
    const encodePlans = buildEncodePlans({
        sourceMimeType,
        hasAlpha: metadata.hasAlpha ?? false,
    });
    let smallest;
    for (const dimensionCap of dimensionCaps) {
        for (const plan of encodePlans) {
            const candidate = await encodeImageCandidate({
                content,
                maxDimension: dimensionCap,
                plan,
            });
            if (!smallest || candidate.content.byteLength < smallest.content.byteLength) {
                smallest = candidate;
            }
            if (candidate.content.byteLength <= VIEW_IMAGE_MODEL_MAX_BYTES) {
                return candidate;
            }
        }
    }
    const targetSizeLabel = formatBytes(VIEW_IMAGE_MODEL_MAX_BYTES);
    if (smallest) {
        throw new Error(`Image could not be reduced below ${targetSizeLabel} (best effort produced ${formatBytes(smallest.content.byteLength)}).`);
    }
    throw new Error(`Image could not be reduced below ${targetSizeLabel}.`);
}
function buildViewImageUiText(args) {
    const { mimeType, fullText } = args;
    const trimmedFullText = fullText.trimEnd();
    const fullLines = trimmedFullText
        ? trimmedFullText.split("\n").map((text) => ({ text }))
        : [{ text: mimeType }];
    return {
        previewLines: [],
        statusLine: mimeType,
        fullLines,
    };
}
export function createViewImageToolDefinition(backend) {
    return {
        schema: VIEW_IMAGE_TOOL,
        async dispatch(toolCall, _riskLevel) {
            const parsedArgs = parseViewImageArgs(toolCall.arguments);
            const path = parsedArgs.ok ? parsedArgs.data.path : "";
            const headerTarget = path || "(invalid arguments)";
            const blocked = (reason) => {
                const toolResult = createToolError(toolCall, reason);
                const uiEvent = {
                    type: "view_image_blocked",
                    toolCallId: toolCall.id,
                    path: path || "(invalid path)",
                    headerTarget,
                    reason,
                };
                return { kind: "single", toolResult, uiEvent };
            };
            if (!parsedArgs.ok) {
                return blocked(`Invalid arguments: ${parsedArgs.error}`);
            }
            try {
                const { path: resolvedPath, content } = await backend.readFileBinary(path, {
                    maxBytes: VIEW_IMAGE_READ_MAX_BYTES,
                });
                const detected = await fileTypeFromBuffer(content);
                const mimeType = detected?.mime;
                if (!isSupportedImageType(mimeType)) {
                    return blocked(`Unsupported image format. Supported formats: ${SUPPORTED_IMAGE_TYPES.join(", ")}.`);
                }
                const encodedImage = await prepareImageForModel(content, mimeType);
                const data = encodedImage.content.toString("base64");
                const resultText = `Viewed ${resolvedPath} (${encodedImage.mimeType})`;
                const toolResult = {
                    role: "toolResult",
                    toolCallId: toolCall.id,
                    toolName: toolCall.name,
                    content: [
                        { type: "text", text: resultText },
                        { type: "image", data, mimeType: encodedImage.mimeType },
                    ],
                    isError: false,
                    timestamp: Date.now(),
                };
                const uiText = buildViewImageUiText({
                    mimeType: encodedImage.mimeType,
                    fullText: resultText,
                });
                const uiEvent = {
                    type: "view_image_success",
                    toolCallId: toolCall.id,
                    path: resolvedPath,
                    headerTarget: resolvedPath,
                    mimeType: encodedImage.mimeType,
                    bytes: encodedImage.content.byteLength,
                    uiText,
                };
                return { kind: "single", toolResult, uiEvent };
            }
            catch (e) {
                const errorMessage = e instanceof Error ? e.message : String(e);
                return blocked(`Tool view_image failed: ${errorMessage}`);
            }
        },
    };
}
//# sourceMappingURL=view_image.js.map