"""Lo que JARVIS oye y que hace con ello. El combustible de todo lo demas.

Sin esto no hay forma de mejorar nada: no se puede arreglar lo que no se mide,
y hasta ahora el nucleo no apuntaba absolutamente nada.

## Por que este y no el de antes

El diario de la epoca de Newelle guardaba `{"texto": "..."}` y nada mas. Con eso
no se puede hacer casi nada:

  - no se sabe **que paso** con la frase, asi que no se distingue una orden que
    fallo de una que funciono;
  - no se sabe **cuando**, asi que no se ve si alguien repitio la misma cosa de
    tres formas seguidas —que es la señal mas valiosa que hay—;
  - y solo guardaba los FALLOS, asi que no habia con que comparar.

Ademas se llenaba de eco y de alucinaciones de Whisper, y la mejora nocturna
acabo generando alias a partir del ruido. De 504 frases, 306 parecian ordenes
sin atender y al mirarlas casi todas eran JARVIS oyendose a si mismo.

## Que se guarda

Todo lo que llega, con su destino. Una linea por frase, JSON:

    t       cuando
    oido    lo que transcribio Whisper
    via     catalogo | cerebro | ruido | bloqueado | respuesta
    accion  a que accion fue, si fue a alguna
    arg     con que argumento
    seg     lo que tardo en contestar
    dicho   lo que contesto

Guardar TAMBIEN los aciertos es lo que permite comparar: sin ellos, un dia malo
y un dia bueno tienen el mismo aspecto.

## Lo que NO se guarda

Nada de audio. El diario es texto y se puede leer con `less`; si algun dia
guardara los WAV, el asistente pasaria a ser una grabadora de la habitacion y
eso es otra cosa muy distinta de lo que es.
"""
import json
import os
import threading
import time
from datetime import datetime

AQUI = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DIARIO = os.path.join(AQUI, "datos", "diario.jsonl")

# Un diario que crece sin fin acaba siendo un problema en vez de una ayuda. A
# ~150 bytes por linea, 200.000 son unos 30 MB: mas que suficiente para meses de
# uso y poco para el disco.
TOPE_LINEAS = 200_000

_cerrojo = threading.Lock()


def anota(oido: str, via: str, accion: str = "", arg: str = "",
          seg: float = 0.0, dicho: str = "", ruta: str = DIARIO):
    """Apunta una frase y su destino. No falla nunca hacia fuera.

    Si escribir el diario diera error, lo ultimo que debe hacer es tirar al
    asistente: se pierde una linea y ya.
    """
    if not oido:
        return
    linea = {
        "t": datetime.now().isoformat(timespec="seconds"),
        "oido": oido[:400],
        "via": via,
    }
    if accion:
        linea["accion"] = accion
    if arg:
        linea["arg"] = arg[:200]
    if seg:
        linea["seg"] = round(seg, 2)
    if dicho:
        linea["dicho"] = dicho[:300]

    try:
        with _cerrojo:
            os.makedirs(os.path.dirname(ruta), exist_ok=True)
            with open(ruta, "a", encoding="utf-8") as f:
                f.write(json.dumps(linea, ensure_ascii=False) + "\n")
    except OSError:
        pass


def lee(ruta: str = DIARIO, desde=None) -> list:
    """Las lineas del diario, ya en diccionarios. Salta las corruptas."""
    salida = []
    try:
        with open(ruta, encoding="utf-8") as f:
            for l in f:
                try:
                    d = json.loads(l)
                except json.JSONDecodeError:
                    continue
                if desde and d.get("t", "") < desde:
                    continue
                salida.append(d)
    except OSError:
        pass
    return salida


def recorta(ruta: str = DIARIO, tope: int = TOPE_LINEAS):
    """Deja solo las ultimas `tope` lineas."""
    lineas = []
    try:
        with open(ruta, encoding="utf-8") as f:
            lineas = f.readlines()
    except OSError:
        return
    if len(lineas) <= tope:
        return
    try:
        with _cerrojo, open(ruta, "w", encoding="utf-8") as f:
            f.writelines(lineas[-tope:])
    except OSError:
        pass
