/**
 * src/media/vision.ts
 * خط أنابيب الرؤية — Image understanding using Qwen 2.5 VL via Together AI
 * OCR, document analysis, and general image description
 */
import { readFile, mkdir } from 'node:fs/promises';
import { extname } from 'node:path';
import { config, MODELS } from '../config.js';
import { createChildLogger } from '../utils/logger.js';
const log = createChildLogger('media:vision');
const TOGETHER_BASE_URL = 'https://api.together.xyz/v1';
const VISION_MODEL = MODELS.vision.model;
const TMP_DIR = '/tmp/muassis-vision';
// ─── Helpers ─────────────────────────────────────────────────────────
async function ensureTmpDir() {
    await mkdir(TMP_DIR, { recursive: true });
}
function getMimeType(filePath) {
    const ext = extname(filePath).toLowerCase();
    const mimeMap = {
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.png': 'image/png',
        '.gif': 'image/gif',
        '.webp': 'image/webp',
        '.bmp': 'image/bmp',
        '.tiff': 'image/tiff',
        '.tif': 'image/tiff',
    };
    return mimeMap[ext] || 'image/jpeg';
}
async function imageToBase64DataUri(imagePath) {
    const buffer = await readFile(imagePath);
    const mime = getMimeType(imagePath);
    const base64 = buffer.toString('base64');
    return `data:${mime};base64,${base64}`;
}
function calculateCost(tokensInput, tokensOutput) {
    const inputCost = (tokensInput / 1_000_000) * MODELS.vision.costPer1MInput;
    const outputCost = (tokensOutput / 1_000_000) * MODELS.vision.costPer1MOutput;
    return Math.round((inputCost + outputCost) * 1_000_000) / 1_000_000; // 6 decimal places
}
/**
 * Call the vision model with an image and a prompt.
 * Uses OpenAI-compatible multimodal format with base64 data URI.
 */
async function callVisionModel(imagePath, systemPrompt, userPrompt, maxTokens = 2048) {
    await ensureTmpDir();
    const dataUri = await imageToBase64DataUri(imagePath);
    const startTime = Date.now();
    const body = {
        model: VISION_MODEL,
        messages: [
            { role: 'system', content: systemPrompt },
            {
                role: 'user',
                content: [
                    { type: 'text', text: userPrompt },
                    {
                        type: 'image_url',
                        image_url: { url: dataUri },
                    },
                ],
            },
        ],
        max_tokens: maxTokens,
        temperature: 0.3,
        stream: false,
    };
    log.debug({ model: VISION_MODEL, imagePath }, 'Calling vision model');
    const response = await fetch(`${TOGETHER_BASE_URL}/chat/completions`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${config.TOGETHER_API_KEY}`,
        },
        body: JSON.stringify(body),
        signal: AbortSignal.timeout(120_000),
    });
    if (!response.ok) {
        const errorText = await response.text().catch(() => 'Unknown error');
        const latencyMs = Date.now() - startTime;
        log.error({ status: response.status, errorText, latencyMs }, 'Vision API call failed');
        throw new Error(`Vision API error: ${response.status} — ${errorText}`);
    }
    const data = await response.json();
    const latencyMs = Date.now() - startTime;
    const content = data.choices?.[0]?.message?.content ?? '';
    const tokensInput = data.usage?.prompt_tokens ?? 0;
    const tokensOutput = data.usage?.completion_tokens ?? 0;
    const tokensUsed = tokensInput + tokensOutput;
    const cost = calculateCost(tokensInput, tokensOutput);
    log.info({ tokensUsed, cost, latencyMs, imagePath }, 'Vision analysis complete');
    return { description: content, tokensUsed, cost };
}
// ─── Public API ──────────────────────────────────────────────────────
/**
 * وصف الصورة — Describe what's in an image, optionally answer a question about it
 */
export async function describeImage(imagePath, question) {
    const systemPrompt = 'أنت مساعد رؤية ذكي. صِف الصور بدقة باللغة العربية. إذا كان السؤال بالإنجليزية، أجب بالإنجليزية. ' +
        'You are a precise vision assistant. Describe images accurately. Match the language of the question.';
    const userPrompt = question
        ? question
        : 'صف هذه الصورة بالتفصيل. ماذا ترى فيها؟ Describe this image in detail.';
    try {
        return await callVisionModel(imagePath, systemPrompt, userPrompt);
    }
    catch (error) {
        log.error({ error, imagePath }, 'Failed to describe image');
        throw error;
    }
}
/**
 * استخراج النص — OCR: extract text from image (Arabic + English)
 */
export async function readTextFromImage(imagePath) {
    const systemPrompt = 'أنت نظام OCR دقيق. استخرج كل النص الموجود في الصورة بالضبط كما هو مكتوب. ' +
        'حافظ على التنسيق والترتيب. إذا كان النص بالعربية، اكتبه بالعربية. إذا كان بالإنجليزية، اكتبه بالإنجليزية. ' +
        'You are a precise OCR system. Extract ALL text from the image exactly as written. ' +
        'Preserve formatting and order. Output only the extracted text, nothing else.';
    const userPrompt = 'استخرج كل النص الموجود في هذه الصورة. اكتب النص فقط بدون أي شرح إضافي. ' +
        'Extract all text from this image. Output only the text, no explanations.';
    try {
        return await callVisionModel(imagePath, systemPrompt, userPrompt, 4096);
    }
    catch (error) {
        log.error({ error, imagePath }, 'Failed to read text from image');
        throw error;
    }
}
/**
 * تحليل المستند — Analyze a document photo (invoice, contract, receipt)
 */
export async function analyzeDocument(imagePath) {
    const systemPrompt = 'أنت محلل مستندات خبير. حلل المستند في الصورة واستخرج المعلومات الرئيسية. ' +
        'حدد نوع المستند (فاتورة، عقد، إيصال، شهادة، الخ). ' +
        'استخرج: الأطراف، التواريخ، المبالغ، البنود الرئيسية، أي أرقام مرجعية. ' +
        'You are an expert document analyst. Analyze the document and extract key information. ' +
        'Identify document type (invoice, contract, receipt, certificate, etc.). ' +
        'Extract: parties, dates, amounts, key terms, reference numbers. ' +
        'Structure your output clearly with headers.';
    const userPrompt = 'حلل هذا المستند بالتفصيل. حدد نوعه واستخرج جميع المعلومات المهمة. ' +
        'Analyze this document in detail. Identify its type and extract all important information.';
    try {
        return await callVisionModel(imagePath, systemPrompt, userPrompt, 4096);
    }
    catch (error) {
        log.error({ error, imagePath }, 'Failed to analyze document');
        throw error;
    }
}
//# sourceMappingURL=vision.js.map