#!/usr/bin/env bash
#
# start_all.sh — ONE command to launch the entire Sanad humanoid stack:
#
#   1. web_nav3 nav stack  → backend :8765 + rosbridge :9090 + map_relay
#                            + internal rosbridge watchdog
#      (runs in its OWN g1_lidar conda env + CycloneDDS)
#
#   2. SanadV3 dashboard   → Gemini voice + LED face + Nav tabs, :8001
#      (runs in its OWN gemini_sdk conda env + Unitree SDK on eth0)
#
# The two stacks use incompatible Python envs + DDS configs, so they CANNOT
# share a process. This launcher starts each detached, supervises the pair
# (restarts one only if it truly dies), and Ctrl+C here stops EVERYTHING.
#
# Liveness is checked by PROCESS PATTERN (pgrep), NOT by a captured PID:
# `setsid ... &` returns the ephemeral setsid-wrapper pid which exits
# instantly, so a pid check would false-fire and restart-storm (which then
# makes each failed relaunch's cleanup trap nuke the working rosbridge).
#
# Usage:   bash ~/Sanadv3/shell_scripts/start_all.sh
# Env knobs (optional): WEBNAV3_HOME, SANAD_HOME, PORT, LOG_DIR, NO_WEBNAV3=1

set -u

WEBNAV3_HOME="${WEBNAV3_HOME:-$HOME/web_nav3}"
SANAD_HOME="${SANAD_HOME:-$HOME/Sanadv3}"
SANAD_PORT="${PORT:-8001}"
LOG_DIR="${LOG_DIR:-$HOME/sanad_logs}"
NO_WEBNAV3="${NO_WEBNAV3:-0}"
mkdir -p "$LOG_DIR"

echo "════════════════════════════════════════════════"
echo "  Sanad humanoid — full stack launcher"
echo "  web_nav3 : $WEBNAV3_HOME $([ "$NO_WEBNAV3" = 1 ] && echo '(skipped)')"
echo "  SanadV3  : $SANAD_HOME  (port $SANAD_PORT)"
echo "  logs     : $LOG_DIR"
echo "════════════════════════════════════════════════"

# liveness by process pattern (robust — no pid tracking)
# NOTE: start_sanad.sh `cd $SANAD_HOME && exec python3 main.py --port N`, so the
# process cmdline is "python3 main.py --port <PORT>" (no Sanadv3/ in argv). Match
# on the PORT — also keeps us from ever touching the live Sanad on :8000.
SANAD_PAT="main.py --port $SANAD_PORT"
webnav3_up() { pgrep -f "web/backend.py" >/dev/null 2>&1; }
# Count a SanadV3 as "up" if EITHER main.py is bound OR start_sanad.sh is still
# mid-boot (conda activate + up-to-20s DDS-iface wait + heavy lazy imports come
# BEFORE `exec python3 main.py`, so "main.py --port" is absent from argv during
# that whole window). Matching the booting shell too prevents the supervisor
# from pkill-ing a process that is simply still cold-booting (restart storm).
sanad_up()   { pgrep -f "$SANAD_PAT" >/dev/null 2>&1 || pgrep -f "shell_scripts/start_sanad.sh" >/dev/null 2>&1; }

start_webnav3() {
    setsid bash "$WEBNAV3_HOME/scripts/start_web.sh" \
        > "$LOG_DIR/web_nav3.log" 2>&1 < /dev/null &
    echo "  web_nav3 launching → $LOG_DIR/web_nav3.log"
}
start_sanad() {
    SANAD_HOME="$SANAD_HOME" PORT="$SANAD_PORT" \
        setsid bash "$SANAD_HOME/shell_scripts/start_sanad.sh" \
        > "$LOG_DIR/sanadv3.log" 2>&1 < /dev/null &
    echo "  SanadV3 launching → $LOG_DIR/sanadv3.log"
}

kill_webnav3() {
    pkill -9 -f "scripts/start_web.sh" 2>/dev/null || true
    pkill -9 -f "web/backend.py|rosbridge_websocket|start_rosbridge.sh|start_map_relay.sh|map_relay.py" 2>/dev/null || true
}

# ---- teardown on Ctrl+C / TERM ----
# Installed BEFORE pre-flight/launch so an interrupt during the "clearing
# stale instances" sleep or right after a setsid child is spawned still
# triggers a full teardown (otherwise those detached children would be
# orphaned with no supervisor).
_cleaning=0
cleanup() {
    [ "$_cleaning" = 1 ] && return; _cleaning=1
    echo
    echo "── stopping everything ──"
    # 1. ask the backend to stop the BRINGUP cleanly (it owns the pgid, so a
    #    killpg takes down rtabmap + all Nav2 nodes in one shot).
    curl -s -m 8 -X POST "http://127.0.0.1:8765/api/shutdown" >/dev/null 2>&1 || true
    # 2. graceful TERM: SanadV3 (clean loco stop) + start_web.sh (own trap).
    pkill -TERM -f "$SANAD_PAT"            2>/dev/null || true
    pkill -TERM -f "scripts/start_web.sh" 2>/dev/null || true
    sleep 3
    # 3. force-kill the web_nav3 stack + SanadV3.
    kill_webnav3
    pkill -9 -f "$SANAD_PAT" 2>/dev/null || true
    pkill -9 -f "shell_scripts/start_sanad.sh" 2>/dev/null || true
    # 4. belt-and-suspenders — any orphaned bringup tree (if the backend was
    #    already gone in step 1, killpg never ran).
    pkill -9 -f "scripts/bringup.sh|run_on_jetson.sh|g1_nav2_slam" 2>/dev/null || true
    pkill -9 -f "rtabmap_slam/rtabmap|livox_pcd_bridge|dog_odom_to_tf|pointcloud_to_laserscan" 2>/dev/null || true
    echo "  done."
    exit 0
}
trap cleanup INT TERM

# ---- pre-flight: clear any stale instances so we start clean ----
echo "── clearing any stale instances … ──"
[ "$NO_WEBNAV3" != 1 ] && kill_webnav3
pkill -9 -f "shell_scripts/start_sanad.sh" 2>/dev/null || true
pkill -9 -f "$SANAD_PAT" 2>/dev/null || true
sleep 2

# ---- launch ----
if [ "$NO_WEBNAV3" != 1 ]; then echo "[1/2] starting web_nav3 nav stack …"; start_webnav3; fi
echo "[2/2] starting SanadV3 dashboard …"; start_sanad

# ---- wait for ports, then report ----
echo
echo "── waiting for services to bind (up to ~80s) … ──"
for _ in $(seq 1 40); do
    up8001=$(ss -tnln 2>/dev/null | grep -c ":$SANAD_PORT ")
    up8765=$(ss -tnln 2>/dev/null | grep -c ':8765 ')
    up9090=$(ss -tnln 2>/dev/null | grep -c ':9090 ')
    if [ "$NO_WEBNAV3" = 1 ]; then
        [ "$up8001" -ge 1 ] && break
    else
        [ "$up8001" -ge 1 ] && [ "$up8765" -ge 1 ] && [ "$up9090" -ge 1 ] && break
    fi
    sleep 2
done

IP=$(hostname -I | awk '{print $1}')
st() { [ "$(ss -tnln 2>/dev/null | grep -c "$1")" -ge 1 ] && echo UP || echo DOWN; }
echo
echo "════════════════════════════════════════════════"
echo "  STATUS"
[ "$NO_WEBNAV3" != 1 ] && echo "    web_nav3 backend  :8765   $(st ':8765 ')"
[ "$NO_WEBNAV3" != 1 ] && echo "    rosbridge         :9090   $(st ':9090 ')"
echo "    SanadV3 dashboard :$SANAD_PORT   $(st ":$SANAD_PORT ")"
echo
echo "  OPEN:  http://$IP:$SANAD_PORT      (full dashboard)"
[ "$NO_WEBNAV3" != 1 ] && echo "         http://$IP:8765      (web_nav3 nav only)"
echo
echo "  logs:  tail -f $LOG_DIR/sanadv3.log"
[ "$NO_WEBNAV3" != 1 ] && echo "         tail -f $LOG_DIR/web_nav3.log"
echo "  >>> Ctrl+C here stops EVERYTHING <<<"
echo "════════════════════════════════════════════════"

# ---- supervise: restart a child ONLY if it truly died (pgrep), with grace ----
while true; do
    sleep 10
    if [ "$NO_WEBNAV3" != 1 ] && ! webnav3_up; then
        echo "[supervisor] $(date +%T) web_nav3 down — restarting"
        kill_webnav3        # clear any half-dead remnants first
        sleep 1
        start_webnav3
        sleep 15            # grace: let it boot before the next check
    fi
    if ! sanad_up; then
        echo "[supervisor] $(date +%T) SanadV3 down — restarting"
        pkill -9 -f "shell_scripts/start_sanad.sh" 2>/dev/null || true
        sleep 1
        start_sanad
        # grace MUST exceed worst-case cold boot: up to 20s DDS-iface wait +
        # conda activate + heavy lazy imports (torch/transformers/gemini) +
        # arm.init before uvicorn binds. 15s was shorter than that and caused a
        # restart storm (kill a still-booting instance, relaunch, repeat).
        sleep 45
    fi
done
