"""ask_llm_vision — pregunta multimodal (imagen + texto) al modelo via la API directa de Anthropic.

Wrapper sobre `stream_anthropic_messages` (grupo claude-direct) que construye un mensaje
multimodal `[bloque de imagen base64, bloque de texto]` y devuelve la respuesta del modelo.
Respeta la regla `llm_invocation`: SIEMPRE API directa (claude-direct, arranque 0 con el token
OAuth de Claude Max), NUNCA `claude -p`.

A diferencia de `ask_llm`, que solo manda texto, esta funcion adjunta una imagen — util para
describir, puntuar o clasificar imagenes (p.ej. evaluar el resultado de una generacion ComfyUI).

Impura: lee la imagen de disco y hace una request HTTP a api.anthropic.com.
"""
import base64
import os
import sys

sys.path.insert(0, os.path.dirname(__file__))

from stream_anthropic_messages import stream_anthropic_messages  # noqa: E402

DEFAULT_MODEL = "claude-opus-4-8"

_MEDIA_TYPES = {
    ".png": "image/png",
    ".jpg": "image/jpeg",
    ".jpeg": "image/jpeg",
    ".jpe": "image/jpeg",
    ".gif": "image/gif",
    ".webp": "image/webp",
}


def _media_type_for(path: str) -> str:
    """Deduce el media_type MIME a partir de la extension del archivo (o "" si no se reconoce)."""
    ext = os.path.splitext(path)[1].lower()
    return _MEDIA_TYPES.get(ext, "")


def ask_llm_vision(
    prompt: str,
    image_path: str = "",
    *,
    image_b64: str = "",
    media_type: str = "",
    model: str = DEFAULT_MODEL,
    system: str = "",
    max_tokens: int = 4096,
    echo: bool = False,
    token: str = "",
) -> dict:
    """Pregunta al modelo sobre una imagen y devuelve su respuesta de texto.

    Construye un mensaje multimodal con un bloque de imagen (base64) seguido del bloque de
    texto del prompt, y lo envia a la API Messages de Anthropic via
    `stream_anthropic_messages` (token OAuth de Claude Max, sin proceso `claude`).

    Args:
        prompt: pregunta/instruccion de texto sobre la imagen.
        image_path: ruta a la imagen en disco. Se lee y codifica a base64; el media_type se
            deduce de la extension si no se pasa. Ignorado si se pasa image_b64.
        image_b64: alternativa a image_path: la imagen ya en base64 (sin prefijo `data:`).
            Requiere `media_type` explicito. keyword-only.
        media_type: tipo MIME de la imagen (image/png, image/jpeg, image/webp, image/gif).
            Obligatorio con image_b64; deducido del path si se omite. keyword-only.
        model: id del modelo Anthropic con vision. Default "claude-opus-4-8". keyword-only.
        system: system prompt opcional. keyword-only.
        max_tokens: maximo de tokens de salida. keyword-only.
        echo: si True, vuelca el texto a stdout segun llega (streaming). keyword-only.
        token: token OAuth; si vacio, lo carga `stream_anthropic_messages` automaticamente.
            keyword-only.

    Returns:
        dict ``{ok, text, model, error}``. En exito ``ok=True`` y ``text`` lleva la respuesta
        completa del modelo. En error ``ok=False``, ``text=""`` y ``error`` describe la causa
        (imagen inexistente, falta media_type, error HTTP/API). Nunca lanza excepcion.
    """
    if not image_path and not image_b64:
        return {"ok": False, "text": "", "model": model,
                "error": "falta la imagen: pasa image_path o image_b64"}

    if image_b64:
        b64 = image_b64
        mt = media_type
        if not mt:
            return {"ok": False, "text": "", "model": model,
                    "error": "image_b64 requiere media_type explicito (ej. image/png)"}
    else:
        path = os.path.expanduser(image_path)
        if not os.path.isfile(path):
            return {"ok": False, "text": "", "model": model,
                    "error": f"imagen no encontrada: {path}"}
        mt = media_type or _media_type_for(path)
        if not mt:
            return {"ok": False, "text": "", "model": model,
                    "error": f"no se pudo deducir media_type de {path!r}; pasa media_type explicito"}
        try:
            with open(path, "rb") as fh:
                b64 = base64.standard_b64encode(fh.read()).decode("ascii")
        except OSError as exc:
            return {"ok": False, "text": "", "model": model,
                    "error": f"no se pudo leer la imagen: {exc}"}

    content = [
        {"type": "image", "source": {"type": "base64", "media_type": mt, "data": b64}},
        {"type": "text", "text": prompt},
    ]
    messages = [{"role": "user", "content": content}]

    parts = []
    for ev in stream_anthropic_messages(
        messages=messages,
        model=model,
        system=system,
        max_tokens=max_tokens,
        token=token,
    ):
        t = ev.get("type")
        if t == "text":
            parts.append(ev["text"])
            if echo:
                sys.stdout.write(ev["text"])
                sys.stdout.flush()
        elif t == "error":
            return {"ok": False, "text": "", "model": model,
                    "error": ev.get("message", "error desconocido")}

    if echo:
        sys.stdout.write("\n")
        sys.stdout.flush()
    return {"ok": True, "text": "".join(parts), "model": model, "error": ""}


def _main(argv):
    image_path = ""
    model = DEFAULT_MODEL
    system = ""
    prompt_parts = []
    i = 0
    while i < len(argv):
        a = argv[i]
        if a in ("--image", "-i") and i + 1 < len(argv):
            image_path = argv[i + 1]
            i += 2
        elif a in ("--model", "-m") and i + 1 < len(argv):
            model = argv[i + 1]
            i += 2
        elif a in ("--system", "-s") and i + 1 < len(argv):
            system = argv[i + 1]
            i += 2
        else:
            prompt_parts.append(a)
            i += 1
    prompt = " ".join(prompt_parts).strip()
    if not image_path:
        sys.stderr.write('uso: ask_llm_vision "prompt" --image RUTA [--model M] [--system S]\n')
        return 2
    if not prompt:
        prompt = "Describe esta imagen."
    res = ask_llm_vision(prompt, image_path, model=model, system=system, echo=True)
    if not res["ok"]:
        sys.stderr.write("ask_llm_vision error: " + str(res["error"]) + "\n")
        return 1
    return 0


if __name__ == "__main__":
    sys.exit(_main(sys.argv[1:]))
