#!/bin/bash
# Graceful CTM restart via launchd.
# Tries the API first (graceful shutdown + launchd auto-respawn). If launchd does NOT
# bring CTM back -- it can park CTM's deliberate graceful exit (code 42) instead of
# respawning -- this RECOVERS via a launchd kickstart and verifies again, so an API
# restart never silently leaves CTM down (the old script just exit 1'd here).
# A slow restore-storm boot is NOT force-restarted: we only kickstart when launchd
# reports no running process for the job. NO bash watchers or nohup -- launchd handles respawn.
CTM_PORT="${CTM_PORT:-3456}"
LABEL="com.walle.server"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

_health_ok() { curl -sf "http://localhost:${CTM_PORT}/api/health" >/dev/null 2>&1; }

# True iff launchd reports a live (non-zero) pid for the job -- i.e. it respawned CTM,
# even if CTM is still booting and not yet answering /api/health.
_launchd_running() {
  launchctl print "gui/$(id -u)/$LABEL" 2>/dev/null | grep -qE 'pid = [1-9][0-9]*'
}

_kickstart_via_launchd() {
  echo "Recovering via launchd kickstart..."
  local REPO_ROOT NODE_BIN
  REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
  # Run the preflight under the PINNED node (not ambient PATH) so it can never rebuild
  # the native driver to a mismatched ABI. Falls back to PATH node in non-owner installs.
  NODE_BIN="$(bash "$REPO_ROOT/bin/node-bin.sh" 2>/dev/null || command -v node 2>/dev/null || true)"
  if [[ -n "$NODE_BIN" ]]; then
    echo "Checking SQLite native driver before launchd restart (node: $NODE_BIN)..."
    "$NODE_BIN" "$SCRIPT_DIR/check-sqlite-driver.js" >/tmp/ctm-sqlite-driver-check.log 2>&1 || {
      echo "SQLite driver preflight failed; launchd startup will retry self-repair."
      tail -20 /tmp/ctm-sqlite-driver-check.log 2>/dev/null || true
    }
  fi
  if launchctl kickstart -k "gui/$(id -u)/$LABEL" 2>/dev/null; then
    echo "Kicked via launchd."
  elif launchctl start "$LABEL" 2>/dev/null; then
    echo "Started via launchd."
  else
    echo "ERROR: launchd service '$LABEL' not installed."
    echo "Install: launchctl load ~/Library/LaunchAgents/com.walle.server.plist"
    return 1
  fi
}

# Wait up to $1 seconds for /api/health. After a startup grace, if launchd shows no
# running pid for 5 consecutive seconds, conclude it parked the exit and stop early so
# the caller can kickstart. Returns: 0 = healthy, 2 = parked (needs kickstart), 1 = timeout-while-running.
_await_health() {
  local deadline="${1:-120}" grace=8 parked=0 i
  for i in $(seq 1 "$deadline"); do
    _health_ok && return 0
    if (( i > grace )); then
      if _launchd_running; then parked=0; else parked=$(( parked + 1 )); fi
      (( parked >= 5 )) && return 2
    fi
    sleep 1
  done
  return 1
}

# 1) Try the graceful API restart first (works when CTM is running).
RESULT="$(curl -s -X POST "http://localhost:${CTM_PORT}/api/restart/ctm?force=true" 2>/dev/null)"
if [[ $? -eq 0 && "$RESULT" == *'"ok":true'* ]]; then
  echo "CTM restarting via API (launchd should respawn)..."
  sleep 2
  _await_health 120
  case $? in
    0) echo "CTM server is back up."; exit 0 ;;
    1) echo "CTM is running but slow to answer /api/health (heavy boot) -- not force-restarting a live boot."; exit 1 ;;
    2) echo "launchd did NOT respawn CTM (parked the graceful exit). Recovering..." ;;  # fall through to kickstart
  esac
else
  echo "API unreachable. Attempting launchd restart..."
fi

# 2) Recover via launchd kickstart (parked-exit path, or API unreachable).
_kickstart_via_launchd || exit 1
sleep 2
if _await_health 120; then
  echo "CTM server is back up."
  exit 0
fi
echo "CTM server did not come back after kickstart recovery (within timeout)."
exit 1
