#!/usr/bin/env bash
set -uo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
RUN_DIR="$ROOT_DIR/.run"
PID_FILE="$RUN_DIR/dev-all.pids"

# Processes actually spawned by dev-all.sh look like this on the command
# line (dev-all.sh always `cd`s into the target dir first, so the binary is
# invoked with a *relative* path — never anchor these on an absolute path).
# Env-var assignments (e.g. HAIXUN_NODE_WORKER_TYPE=...) must never be used
# as a match pattern: once `env FOO=bar cmd` execs `cmd`, the assignment is
# gone from the process's argv/cmdline and can never match again.
PATTERNS=(
  "go workers|bin/worker -f etc/gateway.worker.yaml"
  "gateway|bin/gateway -f etc/gateway.yaml"
  "node style-8d worker|style-8d-worker.ts"
  "keyword-search tool|keyword-search-server.ts"
  "resolve-media tool|resolve-media-server.ts"
  "web dev server|vite.*--strictPort"
)

port_in_use() {
  local port="$1"
  ss -tlnp 2>/dev/null | grep -qE ":${port} "
}

free_port() {
  local port="$1"
  if ! port_in_use "$port"; then
    return 0
  fi
  echo "[dev-all-stop] freeing port :$port"
  if command -v fuser >/dev/null 2>&1; then
    fuser -k "${port}/tcp" 2>/dev/null || true
  else
    local line pid
    while IFS= read -r line; do
      for pid in $(grep -oE 'pid=[0-9]+' <<<"$line" | cut -d= -f2); do
        kill "$pid" 2>/dev/null || true
      done
    done < <(ss -tlnp 2>/dev/null | grep -E ":${port} " || true)
  fi
  sleep 0.3
}

# Stop everything dev-all.sh recorded in its pidfile. Each recorded pid is a
# process-group leader (dev-all.sh launches jobs via `setsid`), so signalling
# the negative pid takes down the whole subtree (npm -> tsx -> node, etc.),
# not just the immediate child. This is the reliable path and also covers
# runs where dev-all.sh itself was killed abruptly (e.g. `kill -9`, closed
# terminal) and never got to run its own cleanup trap.
stop_from_pidfile() {
  if [[ ! -f "$PID_FILE" ]]; then
    return 0
  fi
  echo "[dev-all-stop] stopping processes recorded in $PID_FILE"
  local pid label
  while read -r pid label; do
    [[ -n "${pid:-}" ]] || continue
    if kill -0 "$pid" 2>/dev/null; then
      echo "[dev-all-stop]   -> ${label:-pgid $pid} (pgid $pid): TERM"
      kill -TERM -- "-$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
    fi
  done < "$PID_FILE"
  sleep 1
  while read -r pid label; do
    [[ -n "${pid:-}" ]] || continue
    if kill -0 "$pid" 2>/dev/null; then
      echo "[dev-all-stop]   -> ${label:-pgid $pid} (pgid $pid) 仍在執行，改用 KILL"
      kill -KILL -- "-$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
    fi
  done < "$PID_FILE"
  rm -f "$PID_FILE"
}

# Fallback for anything the pidfile doesn't know about: processes started
# before this pidfile mechanism existed, started outside dev-all.sh, or left
# over from a crashed run on a previous checkout. Matches the *actual*
# observed command line, not env vars or absolute paths (see PATTERNS above).
stop_pattern() {
  local label="$1"
  local pattern="$2"
  if pgrep -f "$pattern" >/dev/null 2>&1; then
    echo "[dev-all-stop] stopping leftover: $label"
    pkill -f "$pattern" 2>/dev/null || true
    sleep 0.3
    pkill -9 -f "$pattern" 2>/dev/null || true
  fi
}

stop_from_pidfile

for port in 8890 9101 9102; do
  free_port "$port"
done

for port in $(seq 5173 5199); do
  free_port "$port"
done

for entry in "${PATTERNS[@]}"; do
  stop_pattern "${entry%%|*}" "${entry#*|}"
done

sleep 0.5

# Verify instead of just hoping: print anything that survived so it's
# obvious when a leftover truly needs manual attention.
all_patterns="$(printf '%s|' "${PATTERNS[@]#*|}")"
all_patterns="${all_patterns%|}"
remaining="$(pgrep -f "$all_patterns" 2>/dev/null || true)"
if [[ -n "$remaining" ]]; then
  echo "[dev-all-stop] 警告：仍有殘留程序未關閉：" >&2
  # shellcheck disable=SC2086
  ps -o pid,ppid,cmd -p $(echo "$remaining" | paste -sd, -) 2>/dev/null >&2 || true
  echo "[dev-all-stop] 可手動執行: kill -9 $(echo "$remaining" | paste -sd' ' -)" >&2
else
  echo "[dev-all-stop] 已確認沒有殘留的 dev-all 相關程序"
fi

echo "[dev-all-stop] done"
