#!/usr/bin/env python3
"""Enseña acciones nuevas a partir de lo que no entendió.

    python3 ~/COFRE/CODERS/JARVIS/config/aprender.py

Cada vez que dices algo que no encaja con ninguna accion, el panel lo anota en
config/jarvis-sin-accion.jsonl. Esto lo lee, agrupa lo repetido y pregunta a
que accion pertenece cada frase; lo que decidas se escribe como alias en
config/jarvis-acciones.json, que el catalogo carga ANTES que el del paquete.

Por que asi y no entrenando un modelo: los fallos del reconocedor son
sistematicos, no aleatorios. "abre el correo" se oye "a ver el correo" siempre,
no una de cada diez veces. Un alias lo arregla del todo, se lee, se borra si
molesta y no cuesta un milisegundo. Reentrenar costaria horas para acertar
"casi siempre".
"""
import json
import os
import sys
from collections import Counter

CONFIG = os.path.expanduser("~/.var/app/io.github.qwersyk.Newelle/config")
REGISTRO = os.path.join(CONFIG, "jarvis-sin-accion.jsonl")
PROPIO = os.path.join(CONFIG, "jarvis-acciones.json")
PAQUETE = os.path.expanduser(
    "~/.local/share/flatpak/app/io.github.qwersyk.Newelle/x86_64/master/"
    "active/files/share/newelle/acciones/acciones.json")


def carga_catalogo():
    acciones = {}
    for ruta in (PAQUETE, PROPIO):
        try:
            with open(ruta) as f:
                for a in json.load(f).get("acciones", []):
                    acciones.setdefault(a["id"], a)
        except (OSError, json.JSONDecodeError):
            pass
    return acciones


def carga_fallos():
    """Las frases sin accion, agrupadas por veces que se han dicho."""
    cuenta = Counter()
    try:
        with open(REGISTRO) as f:
            for linea in f:
                try:
                    texto = json.loads(linea).get("texto", "").strip()
                except json.JSONDecodeError:
                    continue
                if texto:
                    cuenta[texto] += 1
    except OSError:
        pass
    return cuenta


def guarda_alias(nuevos):
    """Añade los alias al catalogo propio, respetando lo que ya hubiera."""
    try:
        with open(PROPIO) as f:
            propio = json.load(f)
    except (OSError, json.JSONDecodeError):
        propio = {"version": 1, "acciones": []}

    porid = {a["id"]: a for a in propio.get("acciones", [])}
    catalogo = carga_catalogo()

    for ident, frases in nuevos.items():
        if ident in porid:
            entrada = porid[ident]
        else:
            # se copia la del paquete entera: el catalogo propio se carga antes
            # y sustituye a la original, asi que tiene que traer su comando
            entrada = dict(catalogo[ident])
            porid[ident] = entrada
            propio.setdefault("acciones", []).append(entrada)
        for f in frases:
            if f not in entrada["frases"]:
                entrada["frases"].append(f)

    with open(PROPIO, "w") as f:
        json.dump(propio, f, indent=2, ensure_ascii=False)
    return PROPIO


def main():
    fallos = carga_fallos()
    if not fallos:
        print("No hay nada sin entender todavia.")
        print(f"Se anota solo en {REGISTRO}")
        return 0

    catalogo = carga_catalogo()
    ids = sorted(catalogo)
    print(f"{len(fallos)} frases distintas no encajaron con ninguna accion.\n")

    nuevos = {}
    for texto, veces in fallos.most_common():
        print(f'  "{texto}"   ({veces} {"vez" if veces == 1 else "veces"})')
        print("     escribe el id de la accion, 'l' para listarlos, "
              "Enter para saltar, 'q' para terminar")
        try:
            resp = input("     > ").strip()
        except (EOFError, KeyboardInterrupt):
            print()
            break

        if resp == "q":
            break
        if resp == "l":
            for i, ident in enumerate(ids):
                print(f"       {ident:<24}", end="\n" if i % 3 == 2 else "")
            print()
            try:
                resp = input("     > ").strip()
            except (EOFError, KeyboardInterrupt):
                break
        if not resp:
            continue
        if resp not in catalogo:
            print(f"     '{resp}' no existe, saltada")
            continue

        nuevos.setdefault(resp, []).append(texto)
        print(f"     anotada como alias de {resp}")

    if not nuevos:
        print("\nNada que guardar.")
        return 0

    ruta = guarda_alias(nuevos)
    total = sum(len(v) for v in nuevos.values())
    print(f"\n{total} alias guardados en {ruta}")
    print("Se aplican al abrir la pestaña JARVIS otra vez.")

    if input("¿Vaciar el registro de frases no entendidas? [s/N] ").strip().lower() == "s":
        try:
            os.remove(REGISTRO)
            print("registro vaciado")
        except OSError as e:
            print(f"no se pudo vaciar: {e}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
