#!/usr/bin/env python3
"""
sipi_ia_generador_spir.py - Conecta la clave de API de tu IA (la misma
que ya usa 'SiPi IA' para SiPi normal, configurada una sola vez) con
SPIR: en vez de pedirle al modelo que escriba SiPi normal (nombres de
comando largos, 'fin'/indentacion para los bloques), se le pide que
escriba directamente en SPIR -- generando MUCHO menos texto por
programa equivalente, y por lo tanto mucho mas rapido (menos tokens que
generar, mismo resultado final).

El modelo NUNCA ve SiPi normal en la respuesta: escribe SPIR de punta a
punta, y este modulo lo traduce a SiPi real automaticamente (con
'sipi_ia_lang', ver ese archivo) antes de ejecutarlo o mostrarselo a un
humano. Reusa toda la infraestructura de conexion con el proveedor de
IA que ya existe en 'sipi_ia.py' (misma clave, mismo proveedor, mismo
modelo configurado) -- no hace falta configurar nada aparte.
"""
import re

import sipi_ia
import sipi_ia_lang as spir_lang
from sipi_ia_spec import generar_prompt_sistema

_PROMPT_SPIR_CACHEADO = None


def _prompt_sistema_spir():
    """El prompt de sistema completo (reglas de SPIR + tabla de ~295
    codigos) se genera una sola vez por corrida y se reusa -- es texto
    grande (la tabla completa) y no cambia mientras el programa esta
    corriendo, asi que no tiene sentido reconstruirlo en cada pedido."""
    global _PROMPT_SPIR_CACHEADO
    if _PROMPT_SPIR_CACHEADO is None:
        _PROMPT_SPIR_CACHEADO = (
            generar_prompt_sistema()
            + "\n\nMuy importante: respondé SIEMPRE con un unico bloque de codigo "
              "```spir ... ``` en SPIR (nunca en SiPi normal, nunca mezclado), y como "
              "maximo una linea de explicacion antes del bloque."
        )
    return _PROMPT_SPIR_CACHEADO


def _extraer_bloque_spir(texto):
    coincidencia = re.search(r"```(?:spir)?\n(.*?)```", texto, re.DOTALL)
    if coincidencia:
        return coincidencia.group(1).strip()
    return texto.strip()


def generar_codigo_spir(descripcion, codigo_sipi_actual=None):
    """Le pide al modelo un programa en SPIR a partir de una descripcion
    en lenguaje natural, y devuelve (codigo_spir, codigo_sipi_traducido).
    Si la traduccion falla (el modelo escribio un codigo que no existe,
    o desbalanceo las llaves), se propaga sipi_ia_lang.ErrorTraduccionSpir
    tal cual -- quien llame decide si reintenta pidiendole al modelo que
    se corrija (ver 'generar_con_auto_correccion_spir' mas abajo, que ya
    lo hace solo)."""
    contenido = descripcion
    if codigo_sipi_actual and codigo_sipi_actual.strip():
        codigo_spir_actual = spir_lang.traducir_desde_sipi(codigo_sipi_actual)
        contenido = (
            f"Este es el programa que ya tengo, en SPIR:\n```spir\n{codigo_spir_actual}\n```\n\n"
            f"Lo que necesito: {descripcion}"
        )
    respuesta = sipi_ia._llamar_modelo(
        [{"role": "user", "content": contenido}], _prompt_sistema_spir(),
    )
    codigo_spir = _extraer_bloque_spir(respuesta)
    codigo_sipi = spir_lang.traducir_a_sipi(codigo_spir)
    return codigo_spir, codigo_sipi


def _corregir_spir(codigo_spir, mensaje_error):
    """Analogo a sipi_ia.corregir_error, pero manteniendo TODO el ciclo
    en SPIR: el modelo nunca deja de escribir en SPIR, ni siquiera para
    corregirse -- si tuviera que volver a SiPi normal para corregir un
    error, se perderia toda la ventaja de velocidad en la parte del
    ciclo (generar -> corregir -> generar -> corregir) que mas se repite
    en un programa complejo."""
    contenido = (
        f"Este programa en SPIR tiene un error:\n```spir\n{codigo_spir}\n```\n\n"
        f"Error real:\n{mensaje_error}\n\n"
        "Corregilo y devolveme el programa COMPLETO ya corregido, en SPIR, "
        "en un unico bloque ```spir ... ```."
    )
    respuesta = sipi_ia._llamar_modelo([{"role": "user", "content": contenido}], _prompt_sistema_spir())
    return _extraer_bloque_spir(respuesta)


def generar_con_auto_correccion_spir(descripcion, ruta_motor_sipi, max_intentos=4, on_intento=None):
    """El mismo ciclo escribir->ejecutar->verificar->corregir de
    'sipi_ia.generar_con_auto_correccion', pero el modelo trabaja
    siempre en SPIR (mas rapido de generar en cada vuelta) y este
    modulo se encarga de traducir a SiPi real solo para poder EJECUTARLO
    de verdad contra el motor -- el modelo nunca necesita saber que esa
    traduccion existe.

    'on_intento' recibe (numero, codigo_spir, codigo_sipi, ok, salida, error),
    igual de detallado que la version para SiPi normal, para que un panel
    en el editor pueda mostrar el progreso intento por intento.

    Devuelve un sipi_ia.ResultadoAutoCorreccion (mismo tipo que la
    version normal) cuyo 'codigo_final' es el SiPi YA TRADUCIDO, listo
    para guardar/ejecutar/mostrarle a un humano -- el SPIR intermedio
    solo importa puertas adentro de este ciclo.
    """
    intentos = []
    codigo_spir = None
    codigo_sipi = None
    error_traduccion = None

    try:
        codigo_spir, codigo_sipi = generar_codigo_spir(descripcion)
    except spir_lang.ErrorTraduccionSpir as e:
        error_traduccion = f"(el codigo SPIR generado no traduce: linea {e.linea}: {e})"
        codigo_sipi = None

    for numero in range(1, max_intentos + 1):
        if codigo_sipi is not None:
            ok, salida, error = sipi_ia._ejecutar_codigo_real(ruta_motor_sipi, codigo_sipi)
        else:
            ok, salida, error = False, "", error_traduccion

        registro = {"codigo_spir": codigo_spir, "codigo": codigo_sipi, "salida": salida, "error": error, "ok": ok}
        intentos.append(registro)
        if on_intento:
            try:
                on_intento(numero, codigo_spir, codigo_sipi, ok, salida, error)
            except Exception:
                pass

        if ok:
            return sipi_ia.ResultadoAutoCorreccion(
                True, codigo_sipi, intentos, "El programa corrio sin errores (generado y corregido en SPIR).",
            )
        if ok is None:
            return sipi_ia.ResultadoAutoCorreccion(
                True, codigo_sipi, intentos,
                "El programa parece estar esperando una entrada de teclado ('preguntar'); "
                "no se pudo verificar en automatico, pero no se detecto ningun error real.",
            )
        if numero == max_intentos:
            break

        try:
            codigo_spir = _corregir_spir(codigo_spir or "", error or salida or "Error desconocido")
            codigo_sipi = spir_lang.traducir_a_sipi(codigo_spir)
            error_traduccion = None
        except spir_lang.ErrorTraduccionSpir as e:
            error_traduccion = f"(el codigo SPIR corregido tampoco traduce: linea {e.linea}: {e})"
            codigo_sipi = None
        except sipi_ia.ErrorSiPiIA as e:
            return sipi_ia.ResultadoAutoCorreccion(
                False, codigo_sipi or "", intentos, f"Se corto por un error de SiPi IA: {e}",
            )

    return sipi_ia.ResultadoAutoCorreccion(
        False, codigo_sipi or "", intentos,
        f"Se llego al limite de {max_intentos} intentos sin lograr una ejecucion limpia (en SPIR).",
    )
