#!/usr/bin/env bash
# Pipeline nocturno: JARVIS se mejora solo mientras nadie mira.
#
# Cada ciclo hace lo mismo: mira los datos que ha dejado el uso real, propone
# un cambio, lo aplica, y lo somete a config/puerta.sh. Si la puerta dice que
# no, se revierte ENTERO con git y se pasa al siguiente. Nada se queda sin
# pasar por ahi.
#
# Por que git y no una copia: el arbol ya esta versionado, `git checkout` es
# atomico y deja rastro de lo que se intento. Al terminar la noche se puede ver
# commit a commit que se cambio y por que.
#
#   noche.sh              corre hasta que se le diga que pare
#   noche.sh --ciclos 5   solo cinco vueltas
#   noche.sh --seco       propone y valida, pero NO commitea (para mirar)
#
# Para pararlo:  touch ~/COFRE/CODERS/JARVIS/PARAR
#
# ── Que puede mejorar solo, y que no ────────────────────────────────────────
#
# Se mejora solo lo que se puede VALIDAR solo. Eso es:
#   · el vocabulario: alias nuevos para ordenes que no se entendieron
#   · las frases del catalogo: variantes de las que fallan
#   · la voz: regenerar en la voz buena las frases que cayeron a la de reserva
#   · el prompt: mantenerlo al dia con el catalogo
#   · acciones nuevas a partir de lo que se pidio y no existia
#
# NO se toca solo el motor, el panel ni la logica de la aplicacion. No porque
# sea imposible generarlo, sino porque no hay forma automatica de comprobar que
# un cambio ahi es bueno: la suite dice si algo se rompio, no si algo mejoro.
# Un bucle que reescribe su propio validador acaba pasando siempre.
set -uo pipefail

# ── DESACTIVADO EL 15 DE AGOSTO DE 2026 ────────────────────────────────────
#
# Este bucle modifica el arbol de `newelle/` y COMMITEA solo. Ese arbol esta
# congelado en la etiqueta jarvis-newelle-final desde que el desarrollo se
# mudo a nucleo/: dejarlo correr escribiria commits automaticos sobre un
# repositorio que por definicion ya no cambia.
#
# Se ha quitado tambien su temporizador de systemd —estaba armado para las
# 03:30 y habria saltado solo—, movido a ~/.config/systemd/user/desactivados/.
#
# Esto de aqui es la segunda cerradura: aunque alguien lo lance a mano, se
# niega. Para retomarlo hay que apuntarlo primero al nucleo, y eso no es
# cambiar una ruta: la puerta (config/puerta.sh) valida el catalogo, el
# generador y la suite VIEJOS, y el nucleo tiene los suyos.
if [ "${JARVIS_NOCHE_A_SABIENDAS:-}" != "si" ]; then
    cat >&2 <<'AVISO'
noche.sh esta desactivado.

Modifica y commitea sobre newelle/, que esta congelado en jarvis-newelle-final
desde que el desarrollo se mudo a nucleo/. Su temporizador de systemd tambien
se retiro.

Para retomarlo hay que reescribirlo contra el nucleo: la puerta valida el
catalogo, el generador y la suite viejos.

Si de verdad quieres correrlo tal cual sobre el arbol congelado:
    JARVIS_NOCHE_A_SABIENDAS=si config/noche.sh
AVISO
    exit 78
fi

RAIZ=$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)
cd "$RAIZ"
PARADA=$RAIZ/PARAR
DIARIO=$RAIZ/noche.log
CICLOS=0; SECO=no
while [ $# -gt 0 ]; do
  case "$1" in
    --ciclos) CICLOS=${2:-0}; shift 2;;
    --seco) SECO=si; shift;;
    *) shift;;
  esac
done

log() { printf '%s  %s\n' "$(date +%H:%M:%S)" "$*" | tee -a "$DIARIO"; }

# ── salvaguarda ────────────────────────────────────────────────────────────
cd "$RAIZ/newelle"
if [ -n "$(git status --porcelain)" ]; then
    log "ABORTO: el fork tiene cambios sin commitear. Commitealos o descartalos."
    exit 1
fi
ANCLA=$(git rev-parse HEAD)
git tag -f "noche-$(date +%Y%m%d-%H%M)" >/dev/null 2>&1
log "ancla: $ANCLA  (para volver: git reset --hard $ANCLA)"
cd "$RAIZ"

# ── las mejoras, cada una en su funcion ────────────────────────────────────
# Cada una devuelve 0 si propuso algo, 1 si no habia nada que hacer.

mejora_vocabulario() {
    # Lo que el usuario dijo y no se entendio. Es la fuente mas valiosa porque
    # son fallos REALES, no inventados.
    local registro=$HOME/.var/app/io.github.qwersyk.Newelle/config/jarvis-sin-accion.jsonl
    [ -f "$registro" ] || return 1
    python3 "$RAIZ/config/proponer_alias.py" --aplicar 2>&1 | tail -3
    return ${PIPESTATUS[0]}
}

mejora_frases() {
    # Variantes de las frases del catalogo, generadas con el modelo local y
    # filtradas duro. Sube la cobertura sin tocar comandos.
    python3 "$RAIZ/config/proponer_frases.py" --aplicar 2>&1 | tail -3
    return ${PIPESTATUS[0]}
}

mejora_voz() {
    # Las frases que cayeron a la voz de reserva se regeneran con la clonada.
    [ -s "$RAIZ/voz/misses.log" ] || return 1
    "$RAIZ/voz/clonador-venv/bin/python" "$RAIZ/voz/calienta_cache.py" cpu 2>&1 | tail -2
    return 0
}

mejora_prompt() {
    # El prompt lleva la lista de capacidades: si cambio el catalogo, hay que
    # regenerarlo o el modelo sigue creyendo que sabe hacer otra cosa.
    flatpak run --command=python3 io.github.qwersyk.Newelle//master \
        "$RAIZ/config/set_persona.py" 2>&1 | grep -v WARNING | tail -1
    return 0
}

mejora_reconocimiento() {
    # La unica que genera sus PROPIOS datos: dice las frases del catalogo con
    # varias voces, velocidades y ruidos, las transcribe con el mismo Whisper
    # del asistente, y propone alias para lo que no llega a su accion.
    #
    # Es la que hace productiva la noche aunque nadie hable con JARVIS. Ya trae
    # su propio guardarrail —solo aplica si el acierto sube Y la charla sigue
    # yendo al modelo— y ademas pasa por la puerta como todo lo demas.
    timeout 3000 python3 "$RAIZ/config/mejora_nocturna.py" --ronda --muestras 2 2>&1 | tail -4
    return ${PIPESTATUS[0]}
}

MEJORAS=(mejora_reconocimiento mejora_vocabulario mejora_frases mejora_prompt mejora_voz)

# ── el bucle ───────────────────────────────────────────────────────────────
vuelta=0
aceptados=0; rechazados=0; sin_nada=0
log "=== empieza la noche ==="

while true; do
    [ -f "$PARADA" ] && { log "parada pedida (existe $PARADA)"; break; }
    [ "$CICLOS" -gt 0 ] && [ "$vuelta" -ge "$CICLOS" ] && { log "hechas $CICLOS vueltas"; break; }
    vuelta=$((vuelta+1))

    for mejora in "${MEJORAS[@]}"; do
        [ -f "$PARADA" ] && break
        log "── vuelta $vuelta · $mejora"

        salida=$($mejora 2>&1); resultado=$?
        [ -n "$salida" ] && echo "$salida" | sed 's/^/        /' | tee -a "$DIARIO" >/dev/null

        cd "$RAIZ/newelle"
        cambios=$(git status --porcelain)
        cd "$RAIZ"
        if [ -z "$cambios" ] && [ "$resultado" -ne 0 ]; then
            log "        nada que proponer"
            sin_nada=$((sin_nada+1))
            continue
        fi

        if [ -z "$cambios" ]; then
            log "        aplicado fuera del fork (voz o ajustes), sin commit"
            aceptados=$((aceptados+1))
            continue
        fi

        # ── LA PUERTA ──
        if timeout 2400 "$RAIZ/config/puerta.sh" completa > "$RAIZ/.puerta.txt" 2>&1; then
            if [ "$SECO" = si ]; then
                log "        [seco] habria pasado la puerta; se revierte igual"
                (cd "$RAIZ/newelle" && git checkout -- . && git clean -fd >/dev/null 2>&1)
            else
                (cd "$RAIZ/newelle" && git add -A && \
                 git commit -q -m "auto($mejora): mejora nocturna

Propuesto y validado por config/noche.sh sin intervencion. Paso la
puerta completa: catalogo valido, generador reproducible, emparejador
sin perdidas, 150 acciones respondiendo y la suite en verde.

$(echo "$salida" | head -3)")
                log "        ACEPTADO y commiteado"
                aceptados=$((aceptados+1))
            fi
        else
            log "        RECHAZADO: $(tail -1 "$RAIZ/.puerta.txt")"
            (cd "$RAIZ/newelle" && git checkout -- . && git clean -fd >/dev/null 2>&1)
            rechazados=$((rechazados+1))
        fi
    done

    # Si una vuelta entera no propuso nada, no tiene sentido seguir dando
    # vueltas en vacio: se espera a que el uso genere datos nuevos.
    if [ "$sin_nada" -ge $(( ${#MEJORAS[@]} )) ]; then
        log "una vuelta entera sin nada que hacer; esperando 30 min"
        for _ in $(seq 180); do [ -f "$PARADA" ] && break; sleep 10; done
        sin_nada=0
    fi
done

log "=== fin: $aceptados aceptados, $rechazados rechazados, $vuelta vueltas ==="
cd "$RAIZ/newelle"
log "commits de esta noche:"
git log --oneline "$ANCLA"..HEAD 2>/dev/null | sed 's/^/        /' | tee -a "$DIARIO"
