#!/usr/bin/env bash
#
# Shared helpers for scripts/power-cycle.sh and scripts/clean-start.sh.
# Source it; do not execute it.
#
# Everything here is scoped to a single worktree. Each worktree runs its own
# dev server on its own PORT (see CLAUDE.md), so a restart in one worktree must
# never touch another worktree's processes.

# Echo the PORT for this project: .env.local's PORT, else 3000.
# Tolerates quotes, surrounding whitespace and a trailing `# comment`.
resolve_port() {
  local project_root="$1"
  local env_file="$project_root/.env.local"
  local value=""

  if [ -f "$env_file" ]; then
    value="$(sed -n 's/^[[:space:]]*PORT[[:space:]]*=[[:space:]]*//p' "$env_file" | tail -1)"
    value="${value%%#*}"                       # drop a trailing inline comment
    value="$(printf '%s' "$value" | tr -d '"'\''[:space:]')"
  fi

  if [ -n "$value" ]; then printf '%s' "$value"; else printf '3000'; fi
}

# Refuse to run on deployed servers. Every server environment (dev box,
# staging, production) keeps a server-local ecosystem.config.js in the
# project root for PM2; developer machines never do. Running these scripts
# on a server would SIGKILL the PM2-managed listener via free_port and
# delete .next out from under it. Belt-and-suspenders with any DeployHQ
# exclusion of these scripts from server deployments.
require_local_workstation() {
  local project_root="$1"
  if [ -f "$project_root/ecosystem.config.js" ]; then
    echo "refusing to run: found ecosystem.config.js, so this looks like a" >&2
    echo "PM2-managed server, and this script is for local dev machines only." >&2
    exit 1
  fi
}

# Stop this worktree's dev server and Stripe listener. Never fatal.
#
# Both pkill patterns are scoped: the dev server by its resolved binary path
# under $project_root, the listener by the port it forwards to. A bare
# `pkill -f "next dev"` would kill every worktree's dev server on the machine.
stop_local_dev() {
  local project_root="$1"
  local port="$2"

  pkill -f "$project_root/node_modules/.bin/next dev" 2>/dev/null || true
  pkill -f "stripe listen.*localhost:$port/" 2>/dev/null || true

  # Whatever the patterns missed, the port kill catches. This is inherently
  # scoped: only one process can be listening on this worktree's port.
  free_port "$port"
}

# Kill whatever is listening on $1, escalating to SIGKILL once.
free_port() {
  local port="$1"

  # Portability: lsof ships with macOS; ss (iproute2) is present on stock Ubuntu
  # and WSL2, where lsof and fuser often are not. Stock Git Bash / MSYS has none
  # of them, but the repo requires WSL2 on Windows (see README).
  #
  # Note: xargs -r is a GNU extension and errors on macOS, so it is not used. The
  # non-empty guard below already prevents an empty-input invocation.
  if ! command -v lsof >/dev/null 2>&1 &&
    ! command -v ss >/dev/null 2>&1 &&
    ! command -v fuser >/dev/null 2>&1; then
    printf '[dev-env] note: no lsof, ss or fuser; cannot check port %s.\n' "$port" >&2
    printf '[dev-env] The pattern kill above already stopped this project'\''s dev server.\n' >&2
    printf '[dev-env] If the server still fails to bind, stop the old process yourself.\n' >&2
    return 0
  fi

  _port_pids() {
    if command -v lsof >/dev/null 2>&1; then
      lsof -ti "tcp:$port" 2>/dev/null || true
    elif command -v ss >/dev/null 2>&1; then
      ss -lptnH "sport = :$port" 2>/dev/null | grep -oE 'pid=[0-9]+' | cut -d= -f2 || true
    else
      fuser "$port/tcp" 2>/dev/null | tr -s ' ' '\n' | grep -E '^[0-9]+$' || true
    fi
  }

  local pids
  pids="$(_port_pids)"
  if [ -n "$pids" ]; then
    printf '%s\n' "$pids" | xargs kill 2>/dev/null || true
    sleep 1
  fi

  pids="$(_port_pids)"
  if [ -n "$pids" ]; then
    printf '%s\n' "$pids" | xargs kill -9 2>/dev/null || true
    sleep 1
  fi
}

# Block until the postgres container reports healthy, or give up after ~60s.
# Resolves the container through Compose rather than hardcoding container_name,
# so docker-compose.yml stays the single source of truth. Run from the project root.
wait_for_postgres() {
  local cid status
  for _ in $(seq 1 30); do
    cid="$(docker compose ps -q postgres 2>/dev/null | head -1)"
    if [ -n "$cid" ]; then
      status="$(docker inspect --format '{{.State.Health.Status}}' "$cid" 2>/dev/null || echo missing)"
      if [ "$status" = "healthy" ]; then return 0; fi
    fi
    sleep 2
  done
  return 1
}
