#!/usr/bin/env python3
"""
sipi_ia_tabla.py - Tabla de codigos de "SiPi IA" (SPIR).

SiPi tiene ~295 comandos con nombres largos y descriptivos en español
("diccionario_obtener", "generar_contrasena_segura", "hash_seguro_contrasena")
porque estan pensados para que un HUMANO los lea y entienda de un vistazo.
Para una IA generando codigo, ese mismo nombre largo es puro costo: cada
token de mas que escribe es tiempo/plata gastados repitiendo una palabra
que la IA ya "sabe" que comando es con solo ver un codigo corto. Esta
tabla le da a cada comando un codigo de 1 a 3 caracteres (sigilo '$' +
base36), sin ambiguedad posible con nombres de variables del programa.

Esta tabla se genera UNA VEZ a partir de sipi.COMANDOS_CONOCIDOS y despues
se persiste en 'sipi_ia_tabla.json' junto a este archivo. Fundamental:
los codigos ya asignados NUNCA se reordenan ni reasignan, ni siquiera si
'COMANDOS_CONOCIDOS' cambia de orden en una version nueva del motor --
si eso pasara, un .spir viejo se traduciria mal. Cuando aparecen comandos
nuevos en una version nueva de SiPi, simplemente se les agregan codigos
nuevos al final de la tabla existente, dejando los viejos intactos.
"""
import json
import os

_AQUI = os.path.dirname(os.path.abspath(__file__))
ARCHIVO_TABLA = os.path.join(_AQUI, "sipi_ia_tabla.json")

_ALFABETO_BASE36 = "0123456789abcdefghijklmnopqrstuvwxyz"


def _codigo_base36(indice):
    """0->'0', 1->'1', ..., 35->'z', 36->'10', etc. -- el codigo mas
    corto posible para cada indice, sin ceros a la izquierda raros."""
    if indice == 0:
        return "0"
    digitos = []
    n = indice
    while n > 0:
        n, resto = divmod(n, 36)
        digitos.append(_ALFABETO_BASE36[resto])
    return "".join(reversed(digitos))


def _comandos_del_motor():
    """Lista de comandos reales de SiPi, en el orden en que aparecen en
    el motor (COMANDOS_CONOCIDOS), sin duplicados y preservando el
    primer orden de aparicion (para que la asignacion de codigos, la
    primera vez que se genera la tabla, sea 100% determinista)."""
    import sipi as motor
    vistos = set()
    resultado = []
    for c in motor.COMANDOS_CONOCIDOS:
        if c not in vistos:
            vistos.add(c)
            resultado.append(c)
    return resultado


def _generar_tabla_nueva():
    comandos = _comandos_del_motor()
    comando_a_codigo = {}
    for i, comando in enumerate(comandos):
        comando_a_codigo[comando] = "$" + _codigo_base36(i)
    return comando_a_codigo


def cargar_o_crear_tabla():
    """Devuelve (comando_a_codigo, codigo_a_comando). Si ya existe
    'sipi_ia_tabla.json' la respeta tal cual (estabilidad entre
    versiones); si no existe, la genera de cero a partir del motor
    actual y la guarda para la proxima vez. Si aparecieron comandos
    nuevos en el motor que la tabla vieja no tiene, se les asignan
    codigos nuevos AL FINAL sin tocar los existentes."""
    comando_a_codigo = {}
    if os.path.exists(ARCHIVO_TABLA):
        try:
            with open(ARCHIVO_TABLA, "r", encoding="utf-8") as f:
                comando_a_codigo = json.load(f)
        except (OSError, ValueError):
            comando_a_codigo = {}

    if not comando_a_codigo:
        comando_a_codigo = _generar_tabla_nueva()
    else:
        # Agregar, al final, cualquier comando del motor que la tabla
        # todavia no conozca -- sin renumerar nada existente.
        comandos_actuales = _comandos_del_motor()
        codigos_usados = set(comando_a_codigo.values())
        siguiente_indice = len(comando_a_codigo)
        hubo_nuevos = False
        for comando in comandos_actuales:
            if comando not in comando_a_codigo:
                candidato = "$" + _codigo_base36(siguiente_indice)
                while candidato in codigos_usados:
                    siguiente_indice += 1
                    candidato = "$" + _codigo_base36(siguiente_indice)
                comando_a_codigo[comando] = candidato
                codigos_usados.add(candidato)
                siguiente_indice += 1
                hubo_nuevos = True
        if hubo_nuevos:
            _guardar_tabla(comando_a_codigo)

    if not os.path.exists(ARCHIVO_TABLA):
        _guardar_tabla(comando_a_codigo)

    codigo_a_comando = {v: k for k, v in comando_a_codigo.items()}
    return comando_a_codigo, codigo_a_comando


def _guardar_tabla(comando_a_codigo):
    try:
        with open(ARCHIVO_TABLA, "w", encoding="utf-8") as f:
            json.dump(comando_a_codigo, f, ensure_ascii=False, indent=0, sort_keys=False)
    except OSError:
        pass


if __name__ == "__main__":
    tabla, _ = cargar_o_crear_tabla()
    print(f"Tabla de SiPi IA: {len(tabla)} comandos -> guardada en {ARCHIVO_TABLA}")
