#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Генерация текстовых описаний PNG-страниц через VLM в LM Studio.

Требования:
    - Запущен LM Studio с загруженной моделью (например, qwen3-vl-4b)
    - Сервер: http://127.0.0.1:1234/v1

Использование:
    python vlm_describer.py <output_folder> [--prompt "..."] [--model MODEL]

Результат: <output_folder>/vlm_descriptions.json
"""

import os
import sys
import json
import base64
import argparse
from pathlib import Path
from openai import OpenAI

# ------------------------------------------------------------------
# Конфигурация LM Studio
# ------------------------------------------------------------------
LMSTUDIO_URL = os.environ.get("LMSTUDIO_URL", "http://127.0.0.1:1234/v1")
LMSTUDIO_KEY = os.environ.get("LMSTUDIO_API_KEY", "lm-studio")

DEFAULT_PROMPT = (
    "Опиши этот чертеж подробно. Укажи:\n"
    "- Какой это этаж (если видно)\n"
    "- Какие оси обозначены\n"
    "- Какие размеры указаны\n"
    "- Какие помещения/квартиры видны\n"
    "- Общую компоновку и заметные детали.\n"
    "Отвечай на русском языке."
)

client = OpenAI(base_url=LMSTUDIO_URL, api_key=LMSTUDIO_KEY)


def encode_image(image_path: Path) -> str:
    with open(image_path, "rb") as f:
        return base64.b64encode(f.read()).decode("utf-8")


def describe_image(image_path: Path, model: str, prompt: str) -> str:
    """Отправляет PNG в VLM и получает текстовое описание."""
    b64 = encode_image(image_path)
    data_url = f"data:image/png;base64,{b64}"

    response = client.chat.completions.create(
        model=model,
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": prompt},
                    {"type": "image_url", "image_url": {"url": data_url}},
                ],
            }
        ],
        temperature=0.3,
        max_tokens=512,  # 4B модель быстро устаёт, не гоним длину
    )
    return response.choices[0].message.content.strip()


def process_folder(folder: Path, model: str, prompt: str):
    """Обрабатывает все PNG в папке и сохраняет описания."""
    png_files = sorted(folder.glob("page_*.png"))
    if not png_files:
        print(f"[ERR] В папке {folder} не найдены page_*.png")
        sys.exit(1)

    out_path = folder / "vlm_descriptions.json"
    descriptions = {}

    print(f"[INFO] Найдено {len(png_files)} изображений")
    print(f"[INFO] LM Studio: {LMSTUDIO_URL}")
    print(f"[INFO] Модель: {model}\n")

    for i, png in enumerate(png_files, 1):
        print(f"[{i}/{len(png_files)}] {png.name} ...", end=" ", flush=True)
        try:
            desc = describe_image(png, model, prompt)
            descriptions[png.name] = desc
            print(f"OK ({len(desc)} chars)")
        except Exception as e:
            print(f"ERR: {e}")
            descriptions[png.name] = f"[ERROR] {e}"

    with open(out_path, "w", encoding="utf-8") as f:
        json.dump(descriptions, f, ensure_ascii=False, indent=2)

    print(f"\n[OK] Сохранено: {out_path}")


def main():
    parser = argparse.ArgumentParser(description="VLM-описания PNG через LM Studio")
    parser.add_argument("folder", help="Папка с page_*.png")
    parser.add_argument("--model", default="qwen/qwen3-vl-4b",
                        help="Имя модели в LM Studio (default: qwen/qwen3-vl-4b)")
    parser.add_argument("--prompt", default=DEFAULT_PROMPT,
                        help="Промпт для VLM")
    args = parser.parse_args()

    folder = Path(args.folder)
    process_folder(folder, args.model, args.prompt)


if __name__ == "__main__":
    main()
