"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createVisionServer = createVisionServer;
exports.stopVisionServer = stopVisionServer;
const path = __importStar(require("path"));
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
const zod_1 = require("zod");
const sharp_1 = __importDefault(require("sharp"));
const llm_1 = require("../../services/llm");
const defaults_1 = require("../../modules/defaults");
const logger_1 = require("../../lib/logger");
const storage_1 = require("../../lib/storage");
const vision_prompts_1 = require("./vision-prompts");
const MIME_MAP = {
    '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
    '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml',
};
/** Max dimension for LLM vision APIs (Anthropic limit is 8000px). */
const MAX_IMAGE_DIMENSION = 7680;
const analyzeImageSchema = zod_1.z.object({
    image: zod_1.z.string().describe('Image source: a storage path (e.g. "amazon_initial.png"), ' +
        'a data URI (data:image/png;base64,...), or an https:// URL.'),
    prompt: zod_1.z.string().optional().describe('Optional analysis prompt to guide the model'),
});
const describeImageSchema = zod_1.z.object({
    image: zod_1.z.string().describe('Image source: a storage path, data URI, or https:// URL.'),
    context: zod_1.z.string().optional().describe('Optional context about the image'),
});
/**
 * Resolve an image reference to an LLM-ready content block.
 *
 * Accepts:
 * - data:image/...;base64,... → used directly
 * - https:// URL → passed as-is (OpenAI can fetch; Anthropic translate layer handles)
 * - Storage path (anything else, including file:// prefixed) → read via storage backend
 */
async function resolveImageContent(image) {
    // Already a data URI — pass through
    if (image.startsWith('data:')) {
        return { type: 'image_url', image_url: { url: image } };
    }
    // HTTPS URL — pass through
    if (image.startsWith('https://') || image.startsWith('http://')) {
        return { type: 'image_url', image_url: { url: image } };
    }
    // Storage path — strip file:// prefix if present, read via backend
    const storagePath = image.replace(/^file:\/\//, '');
    const backend = (0, storage_1.getStorageBackend)();
    const { data } = await backend.read(storagePath);
    // Downscale if any dimension exceeds the LLM vision limit
    let imageBuffer = data;
    let outputMime = MIME_MAP[path.extname(storagePath).toLowerCase()] || 'image/png';
    try {
        const meta = await (0, sharp_1.default)(data).metadata();
        if (meta.width && meta.height && (meta.width > MAX_IMAGE_DIMENSION || meta.height > MAX_IMAGE_DIMENSION)) {
            logger_1.loggerRegistry.info(`[lt-mcp:vision] resizing ${meta.width}x${meta.height} -> fit ${MAX_IMAGE_DIMENSION}px`);
            imageBuffer = await (0, sharp_1.default)(data)
                .resize({ width: MAX_IMAGE_DIMENSION, height: MAX_IMAGE_DIMENSION, fit: 'inside' })
                .png()
                .toBuffer();
            outputMime = 'image/png';
        }
    }
    catch (err) {
        logger_1.loggerRegistry.warn(`[lt-mcp:vision] could not check/resize image: ${err.message}`);
    }
    const base64 = imageBuffer.toString('base64');
    return { type: 'image_url', image_url: { url: `data:${outputMime};base64,${base64}` } };
}
function registerTools(srv) {
    // ── analyze_image ───────────────────────────────────────────────
    srv.registerTool('analyze_image', {
        title: 'Analyze Image',
        description: 'Analyze an image and extract structured data: description, text content, and notable objects.',
        inputSchema: analyzeImageSchema,
    }, async (args) => {
        const { hasLLMApiKey } = await Promise.resolve().then(() => __importStar(require('../../services/llm')));
        if (!hasLLMApiKey(defaults_1.LLM_MODEL_SECONDARY)) {
            return {
                content: [{
                        type: 'text',
                        text: JSON.stringify({
                            error: 'LLM API key not configured',
                            description: null,
                            text_content: null,
                            objects: [],
                        }),
                    }],
            };
        }
        const systemPrompt = args.prompt
            ? `${vision_prompts_1.ANALYZE_IMAGE_PROMPT}\n\nAdditional guidance: ${args.prompt}`
            : vision_prompts_1.ANALYZE_IMAGE_PROMPT;
        let response;
        try {
            const imageContent = await resolveImageContent(args.image || args.image_path);
            response = await (0, llm_1.callLLM)({
                model: defaults_1.LLM_MODEL_SECONDARY,
                messages: [
                    { role: 'system', content: systemPrompt },
                    { role: 'user', content: [imageContent] },
                ],
                max_tokens: defaults_1.LLM_MAX_TOKENS_VISION,
                temperature: 0,
            });
        }
        catch (err) {
            logger_1.loggerRegistry.error(`[lt-mcp:vision] analyze_image failed: ${err.message}`);
            return {
                content: [{
                        type: 'text',
                        text: JSON.stringify({
                            error: err.message,
                            description: null,
                            text_content: null,
                            objects: [],
                        }),
                    }],
            };
        }
        const raw = response.content || '';
        logger_1.loggerRegistry.info(`[lt-mcp:vision] analyze_image response length: ${raw.length}`);
        try {
            const cleaned = raw.replace(/^```json\n?|\n?```$/g, '').trim();
            const parsed = JSON.parse(cleaned);
            return {
                content: [{
                        type: 'text',
                        text: JSON.stringify({
                            description: parsed.description || null,
                            text_content: parsed.text_content || null,
                            objects: parsed.objects || [],
                        }),
                    }],
            };
        }
        catch {
            return {
                content: [{
                        type: 'text',
                        text: JSON.stringify({
                            description: raw,
                            text_content: null,
                            objects: [],
                        }),
                    }],
            };
        }
    });
    // ── describe_image ──────────────────────────────────────────────
    srv.registerTool('describe_image', {
        title: 'Describe Image',
        description: 'Generate a detailed description of an image.',
        inputSchema: describeImageSchema,
    }, async (args) => {
        const { hasLLMApiKey } = await Promise.resolve().then(() => __importStar(require('../../services/llm')));
        if (!hasLLMApiKey(defaults_1.LLM_MODEL_SECONDARY)) {
            return {
                content: [{
                        type: 'text',
                        text: JSON.stringify({
                            error: 'LLM API key not configured',
                            description: null,
                        }),
                    }],
            };
        }
        const systemPrompt = args.context
            ? `${vision_prompts_1.DESCRIBE_IMAGE_PROMPT}\n\nContext: ${args.context}`
            : vision_prompts_1.DESCRIBE_IMAGE_PROMPT;
        const response = await (0, llm_1.callLLM)({
            model: defaults_1.LLM_MODEL_SECONDARY,
            messages: [
                { role: 'system', content: systemPrompt },
                {
                    role: 'user',
                    content: [await resolveImageContent(args.image || args.image_path)],
                },
            ],
            max_tokens: defaults_1.LLM_MAX_TOKENS_VISION,
            temperature: 0,
        });
        const raw = response.content || '';
        try {
            const cleaned = raw.replace(/^```json\n?|\n?```$/g, '').trim();
            const parsed = JSON.parse(cleaned);
            return {
                content: [{
                        type: 'text',
                        text: JSON.stringify({ description: parsed.description || raw }),
                    }],
            };
        }
        catch {
            return {
                content: [{
                        type: 'text',
                        text: JSON.stringify({ description: raw }),
                    }],
            };
        }
    });
}
/**
 * Create the Vision MCP server.
 *
 * Returns a fresh McpServer instance each time. The MCP SDK only allows
 * one transport per server, so each consumer needs its own instance.
 */
async function createVisionServer(options) {
    const name = options?.name || 'long-tail-vision';
    const instance = new mcp_js_1.McpServer({ name, version: '1.0.0' });
    registerTools(instance);
    logger_1.loggerRegistry.info(`[lt-mcp:vision] ${name} ready (2 tools registered)`);
    return instance;
}
/**
 * Stop a Vision MCP server instance.
 */
async function stopVisionServer() {
    logger_1.loggerRegistry.info('[lt-mcp:vision] stopped');
}
