#!/bin/bash
# Modalita' AI: la scheda smette di fare il desktop e si dedica al modello.
#
#     skillfish-ai-mode on            spegne il desktop e quello che non serve
#     skillfish-ai-mode on motore    e spegne anche Unsloth Studio, lasciando
#                                    llama-server da solo sulla 8888
#     skillfish-ai-mode off      rimette tutto com'era
#     skillfish-ai-mode stato    dice com'e' adesso, in JSON
#
# PERCHE'
# Su una BC-250 VRAM e RAM sono lo stesso chip: quello che tiene KDE non ce
# l'ha il modello. Misurato sulla .32 mentre girava Studio: plasmashell 384 MB,
# kwin 218, polkit-kde 198, kded6 116, Xorg 116, i portali 154, kdeconnect 81.
# Circa 1,3 GB, che sono la differenza fra un modello che entra e uno che va in
# swap sul disco - e in swap un modello non "rallenta", diventa inservibile.
#
# ⚠️ SPEGNERE IL DESKTOP CHIUDE QUELLO CHE E' APERTO, senza salvare niente.
# Non lo si fa di nascosto: chi lo chiede dal Control Center o dal Remote
# Manager riceve prima una domanda chiara.
#
# ⚠️ E SI DEVE POTER TORNARE INDIETRO DA REMOTO.
# Per questo skillfish-dashboard e ssh NON si toccano mai: se si spegnesse
# anche la dashboard, chi ha acceso la modalita' da un altro computer
# resterebbe davanti a uno schermo nero senza modo di rimettere il desktop, e
# l'unica via sarebbe staccare la corrente.
set -u

SEGNA=/var/lib/skillfish/ai-mode
LOG=/var/log/skillfish-ai-mode.log
log() { echo "$(date -Is) $*" >> "$LOG" 2>/dev/null; }

# I servizi che si possono spegnere: roba da scrivania, inutile mentre la
# scheda fa solo il modello. ⚠️ Non c'e' NIENTE che serva per rientrare:
# niente ssh, niente dashboard, niente rete.
SPEGNIBILI="bluetooth.service cups.service cups-browsed.service avahi-daemon.service
            ModemManager.service packagekit.service upower.service colord.service
            skillfish-conky.service switcheroo-control.service
            accounts-daemon.service fwupd.service power-profiles-daemon.service
            rtkit-daemon.service udisks2.service skillfish-dp-hotswap.service"

# ⚠️ COSA NON SI TOCCA, E PERCHE'.
# ssh e skillfish-dashboard: sono le due strade per tornare indietro.
# skillfish-fand e skillfish-thermal-guard: senza, la scheda cuoce.
# skillfish-vf-governor e skillfish-vf-watchdog: tengono in piedi la GPU.
# skillfish-gpu-util e skillfish-gpu-freq: sono i numeri del cluster.
# NetworkManager, wpa_supplicant, zerotier-one: e' la rete, cioe' la via di
#   ritorno per chi arriva da fuori casa.
# rpcbind e rpc-statd: se ci sono dischi NFS montati, fermarli li pianta.
# smartmontools: guarda il disco, e costa due megabyte.
# systemd-journald: la sua memoria e' quasi tutta file mappati, riprendibili
#   dal kernel quando servono. Fermarlo non regalerebbe niente e ci lascerebbe
#   senza registro proprio mentre gira il carico piu' pesante.

# Il livello scelto: `studio` (Studio resta acceso), `motore` (solo
# llama-server, con la sua pagina web), `motore-api` (solo l'API).
# ⚠️ Lo tiene skillfish-llama in /etc/skillfish/ai-mode.json, non questo script:
# lo devono leggere anche il Control Center e il Remote Manager, e due posti in
# cui scrivere la stessa cosa diventano due posti che si contraddicono.
livello_scelto() {
    local l=""
    [ -x /usr/local/bin/skillfish-llama ] &&
        l=$(/usr/local/bin/skillfish-llama conf 2>/dev/null |
            sed -n 's/.*"livello": *"\([a-z-]*\)".*/\1/p' | head -1)
    echo "${l:-studio}"
}

stato_json() {
    local attiva=false
    [ -f "$SEGNA" ] && attiva=true
    local dm
    dm=$(systemctl is-active display-manager.service 2>/dev/null)
    # La memoria: quella che conta e' "disponibile", non "libera". Linux usa la
    # RAM avanzata per la cache e la restituisce quando serve: guardare "free"
    # farebbe sembrare pieno un sistema che pieno non e'.
    local disp tot
    disp=$(awk '/^MemAvailable:/ {printf "%d", $2 / 1024}' /proc/meminfo)
    tot=$(awk '/^MemTotal:/ {printf "%d", $2 / 1024}' /proc/meminfo)
    local liv motore
    liv=$(livello_scelto)
    motore=$(systemctl is-active skillfish-llama.service 2>/dev/null)
    printf '{"attiva": %s, "desktop": "%s", "livello": "%s", "motore": "%s", "ram_disponibile_mb": %d, "ram_totale_mb": %d}\n' \
           "$attiva" "${dm:-sconosciuto}" "$liv" "${motore:-inactive}" "${disp:-0}" "${tot:-0}"
}

case "${1:-stato}" in
stato)
    stato_json
    ;;

on)
    [ "$(id -u)" = 0 ] || { echo "serve root" >&2; exit 1; }
    if [ -f "$SEGNA" ]; then stato_json; exit 0; fi

    # `skillfish-ai-mode on motore` cambia anche la scelta salvata: chi lo
    # lancia a mano da ssh non deve prima andare a configurare da un'altra
    # parte. Senza argomento vale quello che e' scritto.
    if [ -n "${2:-}" ] && [ -x /usr/local/bin/skillfish-llama ]; then
        /usr/local/bin/skillfish-llama imposta "livello=$2" >/dev/null || {
            echo "livello sconosciuto: $2" >&2; exit 2; }
    fi
    LIVELLO=$(livello_scelto)

    # ⚠️ DA QUI IN POI QUESTO PROCESSO PUO' MORIRE.
    # Fermare il display manager chiude la sessione grafica, e logind termina
    # ogni processo che le appartiene. Se siamo arrivati dal Control Center
    # (finestra → pkexec → helper → questo script) apparteniamo tutti a quella
    # sessione: lo script veniva ucciso a meta', con il segno gia' scritto e i
    # servizi fermati a meta'. Da ssh non succedeva, ed e' per questo che il
    # guasto si vedeva solo premendo il pulsante.
    # L'unita' transitoria non e' figlia della sessione e sopravvive.
    if [ -z "${SKF_AI_STACCATO:-}" ] && [ -d /run/systemd/system ]; then
        # ⚠️ Se non si riesce a staccarsi NON si fallisce: si prosegue qui.
        # Lo stacco e' una precauzione, e una precauzione che rompe il caso
        # normale e' peggio del problema che evita.
        if systemd-run --quiet --collect --unit=skillfish-ai-mode-on \
                --setenv=SKF_AI_STACCATO=1 \
                /usr/local/bin/skillfish-ai-mode on; then
            # Si aspetta il segno, cosi' chi ha chiamato riceve uno stato vero
            # e non un "attiva: false" scritto un istante prima che diventi
            # vera.
            for _ in 1 2 3 4 5 6 7 8 9 10; do
                [ -f "$SEGNA" ] && break
                sleep 1
            done
            stato_json
            exit 0
        fi
        log "systemd-run non riuscito: proseguo dentro la sessione"
    fi

    install -d /var/lib/skillfish
    : > "$SEGNA"

    # Si segna cosa era acceso PRIMA, altrimenti "off" non sa cosa riaccendere
    # e si finirebbe col riaccendere anche quello che l'utente teneva spento.
    for s in $SPEGNIBILI; do
        if [ "$(systemctl is-active "$s" 2>/dev/null)" = active ]; then
            echo "$s" >> "$SEGNA"
            systemctl stop "$s" 2>/dev/null
        fi
    done

    # Il desktop per ultimo: da qui in poi lo schermo e' nero e si torna solo
    # da ssh o dal Remote Manager.
    if [ "$(systemctl is-active display-manager.service 2>/dev/null)" = active ]; then
        echo "display-manager.service" >> "$SEGNA"
        systemctl stop display-manager.service 2>/dev/null
    fi

    # ⚠️ IL PEZZO GROSSO. Studio da fermo tiene 458 MB dei 567 di tutto il
    # sistema (misurato sulla .32 il 13/09/2026), e mentre il modello gira non
    # fa altro che passare le richieste a llama-server, che lancia lui su una
    # porta a caso. Nei livelli `motore` si toglie l'intermediario: stessa
    # porta 8888, stessa chiave, stessa API, 458 MB in piu' al modello.
    if [ "$LIVELLO" != studio ] && [ -x /usr/local/bin/skillfish-llama ]; then
        if [ "$(systemctl is-active skillfish-unsloth.service 2>/dev/null)" = active ]; then
            # nel segno, cosi' tornando al desktop Studio riparte da solo
            echo "skillfish-unsloth.service" >> "$SEGNA"
            systemctl stop skillfish-unsloth.service 2>/dev/null
        fi
        # Si aspetta che la porta si liberi: llama-server sulla 8888 con Studio
        # ancora aggrappato non parte, e il messaggio parla di "address already
        # in use" senza dire di chi.
        for _ in 1 2 3 4 5 6 7 8 9 10; do
            ss -lnt 2>/dev/null | grep -q ":8888 " || break
            sleep 1
        done
        systemctl start skillfish-llama.service 2>/dev/null
        log "modalita' AI livello $LIVELLO: Studio spento, motore nudo acceso"
    fi

    # E adesso lo schermo dice cosa sta succedendo. Prima restava nero: chi
    # passava davanti alla scheda leggeva "si e' piantata", senza un indirizzo
    # per raggiungerla ne' un modo per tornare indietro.
    # ⚠️ Dopo il display manager, non prima: sddm tiene la sua tty e la
    # console nuova non si vedrebbe.
    systemctl start skillfish-ai-console.service 2>/dev/null
    # Lo schermo puo' essere rimasto su un'altra tty: ce lo si porta sopra.
    chvt 1 2>/dev/null || true

    # Le cache: non e' memoria "liberata" (Linux la restituirebbe da solo), ma
    # fa si' che il modello trovi pagine contigue subito invece di aspettare
    # che il kernel gliele recuperi durante il primo caricamento.
    sync
    echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true

    log "modalita' AI accesa: $(tr '\n' ' ' < "$SEGNA")"
    stato_json
    ;;

off)
    [ "$(id -u)" = 0 ] || { echo "serve root" >&2; exit 1; }
    if [ ! -f "$SEGNA" ]; then stato_json; exit 0; fi

    # ⚠️ La console per prima: tiene la tty1, e sddm che parte trovandola
    # occupata si arrende in silenzio.
    systemctl stop skillfish-ai-console.service 2>/dev/null

    # E il motore nudo prima di Studio, o si litigano la porta 8888.
    systemctl stop skillfish-llama.service 2>/dev/null

    # Si riaccende SOLO quello che era acceso prima, e il desktop per primo
    # cosi' chi guarda lo schermo vede subito che si sta tornando indietro.
    if grep -q '^display-manager.service$' "$SEGNA"; then
        systemctl start display-manager.service 2>/dev/null
    fi
    while read -r s; do
        [ -z "$s" ] && continue
        [ "$s" = display-manager.service ] && continue
        systemctl start "$s" 2>/dev/null
    done < "$SEGNA"

    rm -f "$SEGNA"
    log "modalita' AI spenta, desktop rimesso"
    stato_json
    ;;

*)
    echo "uso: $0 [on [studio|motore|motore-api]|off|stato]" >&2
    exit 2
    ;;
esac
exit 0
