#!/usr/bin/env bash
#
# CORE Workbench launcher — one command, env-checked, fail-loud.
#
#   scripts/workbench            # doctor + setup (idempotent) + run both servers
#   scripts/workbench up         # same as no-arg
#   scripts/workbench doctor     # preflight checks only, change nothing
#   scripts/workbench setup      # preflight + install deps, do not start servers
#
# Brings up the Python API (stdlib HTTP, :8765) and the Vite UI (:5173),
# waits until each is actually healthy, then prints the URL. Ctrl+C stops both.
# Override ports with API_PORT / UI_PORT.
#
# The backend runs on pure Python (numpy backend) — no Rust build required.

set -euo pipefail

API_PORT="${API_PORT:-8765}"
UI_PORT="${UI_PORT:-5173}"
API_HOST="127.0.0.1"
UI_HOST="127.0.0.1"
MIN_NODE_MAJOR=20
MIN_PY_MINOR=11  # requires-python >=3.11

# --- resolve repo root from this script's location (works from anywhere) -----
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
VENV="$ROOT/.venv"
UI_DIR="$ROOT/workbench-ui"

bold() { printf '\033[1m%s\033[0m\n' "$*"; }
ok()   { printf '  \033[32m✓\033[0m %s\n' "$*"; }
warn() { printf '  \033[33m!\033[0m %s\n' "$*"; }
die()  { printf '  \033[31m✗ %s\033[0m\n' "$*" >&2; exit 1; }

# ---------------------------------------------------------------------------
# Preflight — every failure prints the exact fix.
# ---------------------------------------------------------------------------
doctor() {
  bold "Preflight"

  command -v uv >/dev/null 2>&1 \
    || die "uv not found. Install it:  curl -LsSf https://astral.sh/uv/install.sh | sh"
  ok "uv $(uv --version 2>/dev/null | awk '{print $2}')"

  command -v node >/dev/null 2>&1 \
    || die "node not found. Install Node >= ${MIN_NODE_MAJOR} (https://nodejs.org or nvm)."
  local node_major
  node_major="$(node -v | sed 's/^v//' | cut -d. -f1)"
  [ "$node_major" -ge "$MIN_NODE_MAJOR" ] \
    || die "node $(node -v) is too old; need >= ${MIN_NODE_MAJOR}. Try: nvm install ${MIN_NODE_MAJOR}"
  ok "node $(node -v)"

  if ! command -v pnpm >/dev/null 2>&1; then
    if command -v corepack >/dev/null 2>&1; then
      warn "pnpm not on PATH — enabling via corepack (uses the pinned version)"
      corepack enable pnpm >/dev/null 2>&1 || true
    fi
    command -v pnpm >/dev/null 2>&1 \
      || die "pnpm not found. Enable it:  corepack enable pnpm   (or: npm i -g pnpm)"
  fi
  ok "pnpm $(pnpm -v)"

  command -v curl >/dev/null 2>&1 || die "curl not found (needed for health checks)."
  ok "curl present"
}

# ---------------------------------------------------------------------------
# Setup — idempotent. Only does work when something is actually missing.
# ---------------------------------------------------------------------------
setup() {
  bold "Setup"

  if [ ! -x "$VENV/bin/python" ]; then
    warn "creating Python venv (.venv) with uv"
    uv venv "$VENV" >/dev/null
  fi
  # Confirm the venv Python meets the minimum.
  local py_minor
  py_minor="$("$VENV/bin/python" -c 'import sys; print(sys.version_info[1])')"
  [ "$py_minor" -ge "$MIN_PY_MINOR" ] \
    || die "venv Python is 3.${py_minor}; need >= 3.${MIN_PY_MINOR}. Recreate: rm -rf .venv && uv venv --python 3.12"
  ok "Python $("$VENV/bin/python" -c 'import platform; print(platform.python_version())')"

  if [ ! -x "$VENV/bin/core" ] || ! "$VENV/bin/python" -c 'import workbench.server' >/dev/null 2>&1; then
    warn "installing the CORE package into .venv (editable) — first run only, may take a minute"
    (cd "$ROOT" && uv pip install --python "$VENV/bin/python" -e . >/dev/null)
  fi
  "$VENV/bin/python" -c 'import workbench.server, core.cognition.pipeline' \
    || die "workbench backend failed to import. Re-run: rm -rf .venv && scripts/workbench setup"
  ok "CORE backend importable (pure Python, no Rust build needed)"

  if [ ! -d "$UI_DIR/node_modules" ]; then
    warn "installing UI dependencies (pnpm, frozen lockfile) — first run only"
    (cd "$UI_DIR" && pnpm install --frozen-lockfile >/dev/null)
  fi
  ok "UI dependencies installed"
}

# ---------------------------------------------------------------------------
# Run — health-gated startup of both servers; Ctrl+C stops both.
# ---------------------------------------------------------------------------
port_responds() { curl -fsS "http://$1:$2/health" >/dev/null 2>&1; }
port_open()     { curl -fsS -o /dev/null "http://$1:$2" 2>/dev/null; }

BACK_PID=""
FRONT_PID=""
cleanup() {
  trap - INT TERM EXIT
  for pid in "$FRONT_PID" "$BACK_PID"; do
    [ -n "$pid" ] && kill -TERM "$pid" 2>/dev/null || true
  done
  sleep 1
  for pid in "$FRONT_PID" "$BACK_PID"; do
    [ -n "$pid" ] && kill -KILL "$pid" 2>/dev/null || true
  done
  printf '\n'; bold "Workbench stopped."
}

run() {
  bold "Start"
  trap cleanup INT TERM EXIT

  if port_responds "$API_HOST" "$API_PORT"; then
    ok "API already healthy on :$API_PORT (reusing)"
  else
    "$VENV/bin/core" workbench api --host "$API_HOST" --port "$API_PORT" >/tmp/core-workbench-api.log 2>&1 &
    BACK_PID=$!
    printf '  … waiting for API on :%s' "$API_PORT"
    for _ in $(seq 1 60); do
      if port_responds "$API_HOST" "$API_PORT"; then printf '\r'; ok "API healthy on :$API_PORT          "; break; fi
      kill -0 "$BACK_PID" 2>/dev/null || die "API process exited. Logs: /tmp/core-workbench-api.log"
      printf '.'; sleep 1
    done
    port_responds "$API_HOST" "$API_PORT" || die "API did not become healthy in 60s. Logs: /tmp/core-workbench-api.log"
  fi

  if port_open "$UI_HOST" "$UI_PORT"; then
    ok "UI already serving on :$UI_PORT (reusing)"
  else
    # Invoke vite directly (not via `pnpm dev`) so FRONT_PID is the server
    # itself — a pnpm wrapper would not forward the stop signal to vite.
    (cd "$UI_DIR" && exec node_modules/.bin/vite --host "$UI_HOST" --port "$UI_PORT" --strictPort) >/tmp/core-workbench-ui.log 2>&1 &
    FRONT_PID=$!
    printf '  … waiting for UI on :%s' "$UI_PORT"
    for _ in $(seq 1 60); do
      if port_open "$UI_HOST" "$UI_PORT"; then printf '\r'; ok "UI serving on :$UI_PORT          "; break; fi
      kill -0 "$FRONT_PID" 2>/dev/null || die "UI process exited. Logs: /tmp/core-workbench-ui.log"
      printf '.'; sleep 1
    done
    port_open "$UI_HOST" "$UI_PORT" || die "UI did not start in 60s. Logs: /tmp/core-workbench-ui.log"
  fi

  printf '\n'
  bold "CORE Workbench is up:"
  printf '    \033[1;36mhttp://%s:%s\033[0m   (API: http://%s:%s)\n\n' "$UI_HOST" "$UI_PORT" "$API_HOST" "$API_PORT"
  printf '  Press Ctrl+C to stop.\n'

  # Stay in the foreground; if either child dies, surface it and exit.
  while true; do
    [ -n "$BACK_PID" ]  && ! kill -0 "$BACK_PID"  2>/dev/null && die "API exited unexpectedly. Logs: /tmp/core-workbench-api.log"
    [ -n "$FRONT_PID" ] && ! kill -0 "$FRONT_PID" 2>/dev/null && die "UI exited unexpectedly. Logs: /tmp/core-workbench-ui.log"
    sleep 2
  done
}

case "${1:-up}" in
  doctor) doctor ;;
  setup)  doctor; setup ;;
  up|"")  doctor; setup; run ;;
  *) die "unknown command '$1' (use: doctor | setup | up)" ;;
esac
