#!/usr/bin/env bash
# kill_fleet_agent — cierre limpio y dirigido de UN ejecutor de la flota tmux.
#
# Dado un sessionId (o prefijo) o un PID, mata el proceso claude del ejecutor con
# SIGTERM (cierre limpio) y cierra su window tmux en el socket del perfil
# FleetView. Es la pieza que usa el orquestador para liberar el slot idle de cada
# ejecutor en cuanto verifica que su DoD-contrato esta `met`: sin esto, los
# ejecutores terminados se acumulan en reposo en la flota.
#
# Guards de seguridad (NO destruye a quien no debe):
#   - NO mata a un agente con role=orchestrator (leido de su goal.json). Matar un
#     orquestador por error decapitaria la flota.
#   - NO se mata a si mismo (la sesion que invoca la funcion): resuelve el PID de
#     claude actual subiendo por los ancestros de /proc y rechaza el target si
#     coincide. Es el equivalente dirigido de la regla "nunca pkill claude".
#
# Funcion IMPURA: manda SIGTERM a un proceso y cierra una window tmux. Por
# defecto EJECUTA (es el caso de uso del bot: cerrar un ejecutor ya verificado,
# recuperable luego con `claude --resume <sessionId>`). Usa --dry-run para ver el
# plan sin tocar nada.
#
# Overrides de entorno (testabilidad, no para uso normal):
#   FN_FLEET_SESSIONS_DIR  directorio de los sessions JSON. Default ~/.claude/sessions
#   FN_FLEET_GOALS_DIR     directorio de los goal JSON.     Default ~/.claude/goals
#   FN_FLEET_SELF_PID      fuerza el PID propio (salta la deteccion por /proc)
set -euo pipefail
IFS=$' \t\n'

# Predicado (puro respecto a tmux): dada una window — su nombre y el texto de sus
# panes en formato "<pane_pid> <pane_current_command>" (una linea por pane) —
# decide si esa window ALOJA la TUI fleetview o es la window 'console' del perfil.
# Si es asi, cerrar la window entera con kill-window se llevaria la TUI por
# delante; el caller debe cerrar solo el pane del target con kill-pane.
#   - Nombre de window 'console' = la window del panel FleetView por convencion
#     del launcher (y a donde el focus-swap ancla la TUI, ver fleetview v0.4.3).
#   - Algun pane corre el binario 'fleetview' (pane_current_command) = la TUI
#     vive ahi aunque la window se haya renombrado.
# Devuelve 0 si aloja la TUI/console, 1 si no.
_fleet_window_hosts_tui() {
    local window_name="${1:-}" panes_text="${2:-}"
    [[ "$window_name" == "console" ]] && return 0
    if printf '%s\n' "$panes_text" | awk '{print $2}' | grep -qx 'fleetview'; then
        return 0
    fi
    return 1
}

kill_fleet_agent() {
    local target="" socket="" dry=0

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --socket)  shift; socket="${1:-}" ;;
            --dry-run) dry=1 ;;
            -h|--help)
                cat <<'USAGE'
Uso: kill_fleet_agent <sessionId|PID> [--socket <s>] [--dry-run]

Cierra UN ejecutor de la flota: SIGTERM al proceso claude + kill-window de su
window tmux. Resuelve el target por sessionId (exacto o por prefijo) o por PID.

Guards: NO mata a un role=orchestrator ni a la sesion que invoca la funcion.

Opciones:
  --socket <s>   Socket tmux del perfil FleetView donde vive la window.
                 Default: $FLEET_SOCKET, o "fleet" si no esta seteada.
  --dry-run      Imprime el plan (PID, sessionId, role, window, accion) y NO
                 mata ni cierra nada.
  -h, --help     Esta ayuda.

Salida: exit 0 ok (o dry-run); 2 uso incorrecto / target no resuelto; 3 guard
(intento de matar a un orquestador o a la sesion actual).

Ejemplos:
  kill_fleet_agent 32945650-a4e1-472b-90c9-5b38ef60a463   # por sessionId
  kill_fleet_agent 32945650 --socket fleet2               # por prefijo de sessionId
  kill_fleet_agent 48213 --dry-run                         # por PID, solo ver el plan
USAGE
                return 0 ;;
            --*)
                echo "kill_fleet_agent: opcion desconocida '$1' (usa -h)" >&2
                return 2 ;;
            *)
                if [[ -z "$target" ]]; then
                    target="$1"
                else
                    echo "kill_fleet_agent: argumento extra '$1' (target ya es '$target')" >&2
                    return 2
                fi ;;
        esac
        shift
    done

    [[ -z "$target" ]] && {
        echo "kill_fleet_agent: falta el target (sessionId o PID). Usa -h." >&2
        return 2
    }

    local sessions_dir="${FN_FLEET_SESSIONS_DIR:-$HOME/.claude/sessions}"
    local goals_dir="${FN_FLEET_GOALS_DIR:-$HOME/.claude/goals}"
    [[ -z "$socket" ]] && socket="${FLEET_SOCKET:-fleet}"

    command -v jq >/dev/null 2>&1 || {
        echo "kill_fleet_agent: jq no esta instalado (necesario para leer los JSON)" >&2
        return 1
    }

    # -----------------------------------------------------------------------
    # Resolver (PID, sessionId) a partir del target.
    # -----------------------------------------------------------------------
    local pid="" sid=""
    if [[ "$target" =~ ^[0-9]+$ ]]; then
        # target = PID. El sessionId sale de sessions/<pid>.json (si existe).
        pid="$target"
        local sfile="$sessions_dir/$pid.json"
        if [[ -f "$sfile" ]]; then
            sid="$(jq -r '.sessionId // ""' "$sfile" 2>/dev/null || true)"
        fi
    else
        # target = sessionId (exacto o prefijo). Buscar en sessions/*.json el JSON
        # cuyo .sessionId case; el nombre del archivo (<pid>.json) da el PID.
        local f base candidate_sid
        for f in "$sessions_dir"/*.json; do
            [[ -f "$f" ]] || continue
            candidate_sid="$(jq -r '.sessionId // ""' "$f" 2>/dev/null || true)"
            [[ -z "$candidate_sid" ]] && continue
            if [[ "$candidate_sid" == "$target" || "$candidate_sid" == "$target"* ]]; then
                base="$(basename "$f" .json)"
                pid="$base"
                sid="$candidate_sid"
                break
            fi
        done
    fi

    [[ -z "$pid" ]] && {
        echo "kill_fleet_agent: no se pudo resolver el target '$target' a un PID (sessions en $sessions_dir)" >&2
        return 2
    }

    # -----------------------------------------------------------------------
    # Guard 1 — anti-self: no matar a la sesion que invoca la funcion.
    # -----------------------------------------------------------------------
    local self_pid="${FN_FLEET_SELF_PID:-}"
    if [[ -z "$self_pid" ]]; then
        local walk="$$" guard=0 comm
        while [[ -n "$walk" && "$walk" != "0" && "$walk" != "1" ]]; do
            comm="$(cat "/proc/$walk/comm" 2>/dev/null || true)"
            if [[ "$comm" == "claude" ]]; then
                self_pid="$walk"
                break
            fi
            walk="$(awk '{print $4}' "/proc/$walk/stat" 2>/dev/null || true)"
            guard=$((guard + 1))
            [[ "$guard" -gt 64 ]] && break
        done
    fi
    if [[ -n "$self_pid" && "$pid" == "$self_pid" ]]; then
        echo "kill_fleet_agent: REHUSADO — el target (PID $pid) es la sesion actual. No me suicido." >&2
        return 3
    fi

    # -----------------------------------------------------------------------
    # Guard 2 — anti-orquestador: no matar a un role=orchestrator.
    # -----------------------------------------------------------------------
    local role=""
    if [[ -n "$sid" ]]; then
        local gfile="$goals_dir/$sid.json"
        [[ -f "$gfile" ]] && role="$(jq -r '.role // ""' "$gfile" 2>/dev/null || true)"
    fi
    if [[ "$role" == "orchestrator" ]]; then
        echo "kill_fleet_agent: REHUSADO — el target (sessionId ${sid:-?}, PID $pid) tiene role=orchestrator. No se mata al orquestador." >&2
        return 3
    fi

    # -----------------------------------------------------------------------
    # Resolver la window tmux Y el pane del PID en el socket (pane_pid == claude
    # por el `exec claude` de spawn_fleet_agent). Capturamos window_id, pane_id y
    # window_name juntos. Best-effort: vacio si no hay socket.
    # -----------------------------------------------------------------------
    local window="" pane="" wname=""
    if command -v tmux >/dev/null 2>&1; then
        local line
        line="$(tmux -L "$socket" list-panes -a -F '#{pane_pid} #{window_id} #{pane_id} #{window_name}' 2>/dev/null \
                | awk -v p="$pid" '$1==p {print $2, $3, $4; exit}' || true)"
        if [[ -n "$line" ]]; then
            window="$(awk '{print $1}' <<<"$line")"
            pane="$(awk '{print $2}' <<<"$line")"
            wname="$(awk '{print $3}' <<<"$line")"
        fi
    fi

    # -----------------------------------------------------------------------
    # Guard 3 — anti-TUI/console: si la window del target aloja la TUI fleetview
    # o es la window 'console' del perfil, NO cerramos la window entera (eso se
    # llevaria la TUI), sino solo el pane del target con kill-pane. El layout
    # FleetView mete la TUI y un Claude en la misma window 'console', y los
    # focus-swaps (join-pane) pueden meter al ejecutor target en esa window.
    # -----------------------------------------------------------------------
    local hosts_tui=0
    if [[ -n "$window" ]] && command -v tmux >/dev/null 2>&1; then
        local panes_text
        panes_text="$(tmux -L "$socket" list-panes -t "$window" -F '#{pane_pid} #{pane_current_command}' 2>/dev/null || true)"
        if _fleet_window_hosts_tui "$wname" "$panes_text"; then
            hosts_tui=1
        fi
    fi

    # Accion sobre la window/pane segun lo resuelto y el Guard 3.
    local action
    if [[ -z "$window" ]]; then
        action="solo SIGTERM (window no resuelta)"
    elif [[ "$hosts_tui" -eq 1 ]]; then
        if [[ -n "$pane" ]]; then
            action="kill-pane $pane (window '${wname:-$window}' aloja la TUI/console; se preserva la TUI)"
        else
            action="solo SIGTERM (window '${wname:-$window}' aloja la TUI y no se resolvio el pane; window preservada)"
        fi
    else
        action="kill-window $window"
    fi

    # -----------------------------------------------------------------------
    # Plan (se imprime siempre).
    # -----------------------------------------------------------------------
    echo "kill_fleet_agent — target: $target  PID: $pid  sessionId: ${sid:-?}  role: ${role:-executor}  socket: $socket  window: ${window:-(no resuelta)}  pane: ${pane:-?}  accion: $action"

    if [[ "$dry" -eq 1 ]]; then
        echo "DRY-RUN: no se ha matado el proceso ni cerrado nada."
        return 0
    fi

    # -----------------------------------------------------------------------
    # Ejecutar: SIGTERM al claude (cierre limpio) + cierre de pane/window segun
    # el Guard 3 (idempotente).
    # -----------------------------------------------------------------------
    if kill -0 "$pid" 2>/dev/null; then
        kill "$pid" 2>/dev/null || true
        echo "kill_fleet_agent: SIGTERM enviado a claude PID $pid."
    else
        echo "kill_fleet_agent: PID $pid ya no esta vivo (nada que matar)."
    fi

    if [[ -n "$window" ]] && command -v tmux >/dev/null 2>&1; then
        if [[ "$hosts_tui" -eq 1 ]]; then
            if [[ -n "$pane" ]]; then
                tmux -L "$socket" kill-pane -t "$pane" 2>/dev/null || true
                echo "kill_fleet_agent: pane $pane cerrado (window '${wname:-$window}' aloja la TUI; window preservada)."
            else
                echo "kill_fleet_agent: window '${wname:-$window}' aloja la TUI pero no se resolvio el pane; solo SIGTERM (window preservada)."
            fi
        else
            tmux -L "$socket" kill-window -t "$window" 2>/dev/null || true
            echo "kill_fleet_agent: window $window cerrada en el socket $socket."
        fi
    fi

    return 0
}

# Permitir ejecutar el archivo directamente (no solo como funcion sourced).
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    kill_fleet_agent "$@"
fi
