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

job_dir="$1"
mirror="$2"
rel="$3"
shift 3
mkdir -p "$job_dir"
exec >> "$job_dir/log" 2>&1

rc=254
write_rc() {
  local tmp="$job_dir/.rc.tmp.$$.$RANDOM"
  printf '%s\n' "$1" > "$tmp" && mv -f "$tmp" "$job_dir/rc"
}
finish() {
  trap - EXIT HUP INT TERM QUIT
  [ "$rc" -eq 255 ] && rc=254
  local receipt_rc="$rc"
  write_rc "$receipt_rc"
  exit "$receipt_rc"
}
interrupt() {
  rc="$1"
  exit "$rc"
}
trap 'interrupt 129' HUP
trap 'interrupt 130' INT
trap 'interrupt 131' QUIT
trap 'interrupt 143' TERM
trap finish EXIT

# Load canonical per-user environment for non-login build jobs.
for env_file in "$HOME"/.config/environment.d/*.conf; do
  [ -r "$env_file" ] || continue
  set -a
  # shellcheck disable=SC1090
  . "$env_file"
  set +a
done

# Build boxes often inherit DISPLAY from a forwarded X11 session on the workstation.
# Headless browsers and GTK still probe X when DISPLAY is set, which paints brief
# windows on Cinnamon before exiting.
unset DISPLAY WAYLAND_DISPLAY
export GDK_BACKEND=x11

export PATH="$HOME/.cargo/bin:$HOME/.local/bin:$PATH"
if [ -x "$HOME/.local/bin/mise" ]; then
  eval "$("$HOME/.local/bin/mise" env -s bash)"
fi
# corepack shims in ~/.local/bin resolve pnpm per-mirror from packageManager;
# must outrank any mise-pinned pnpm a rival session re-adds
export PATH="$HOME/.local/bin:$PATH"
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
# A gate run dispatched here must not dispatch again from the box.
export GATE0_REMOTE_ACTIVE=1

if [ -z "${NODE_AUTH_TOKEN:-}" ] && [ -f "$HOME/.npmrc" ]; then
  NODE_AUTH_TOKEN=$(sed -n 's/.*_authToken=//p' "$HOME/.npmrc" | head -1)
  [ -n "$NODE_AUTH_TOKEN" ] && export NODE_AUTH_TOKEN
fi

# Workspace root = nearest dir with a lockfile, from the job cwd up to the mirror
# root (a JS workspace may sit nested inside a non-JS repo, e.g. backend/ in a PHP repo).
ws_root="$mirror"
d="$mirror${rel:+/$rel}"
while :; do
  if [ -f "$d/pnpm-lock.yaml" ] || [ -f "$d/package-lock.json" ] || [ -f "$d/bun.lock" ] || [ -f "$d/bun.lockb" ]; then
    ws_root="$d"
    break
  fi
  [ "$d" = "$mirror" ] && break
  d=$(dirname "$d")
done

cd "$ws_root" || exit 254
# node_modules is a derived cache and is never trusted: a rival package manager's
# layout (bun/npm over a pnpm workspace) or a broken link farm makes frozen installs
# no-op while builds die on phantom resolve errors. Every dependency of every
# workspace package must resolve to a real directory, or the tree is rebuilt once
# from the registry; still broken → fail closed with the store as prime suspect.
verify_pnpm_tree() {
  if [ -d node_modules/.bun ]; then
    echo "remote-runner: foreign bun layout in pnpm workspace $ws_root" >&2
    return 1
  fi
  local chk="$job_dir/depcheck.js"
  cat > "$chk" <<'NODE'
const fs = require("fs");
const path = require("path");
const p = JSON.parse(fs.readFileSync("package.json", "utf8"));
const deps = Object.keys(Object.assign({}, p.dependencies, p.devDependencies));
let bad = 0;
for (const d of deps) {
  let dir = process.cwd();
  let ok = false;
  for (;;) {
    if (fs.existsSync(path.join(dir, "node_modules", d, "package.json"))) { ok = true; break; }
    const up = path.dirname(dir);
    if (up === dir) break;
    dir = up;
  }
  if (!ok) { console.error(`depcheck: missing dependency ${d} of ${p.name || "root"}`); bad = 1; }
}
process.exit(bad);
NODE
  pnpm -r exec node "$chk"
}

install_rc=0
if [ -f pnpm-lock.yaml ]; then
  lockf=pnpm-lock.yaml
  new_hash=$(sha256sum "$lockf" | cut -d' ' -f1)
  old_hash=$(cat .rb-lockhash 2>/dev/null || true)
  if [ "$new_hash" != "$old_hash" ] || [ ! -d node_modules ]; then
    pnpm install --frozen-lockfile --prefer-offline --config.confirmModulesPurge=false
    install_rc=$?
    [ "$install_rc" -eq 0 ] && printf '%s' "$new_hash" > .rb-lockhash
  fi
  if [ "$install_rc" -eq 0 ] && ! verify_pnpm_tree; then
    echo "remote-runner: node_modules failed integrity check — rebuilding from scratch" >&2
    find . -maxdepth 3 -type d -name node_modules -prune -exec rm -rf {} +
    rm -f .rb-lockhash
    pnpm install --frozen-lockfile --force --config.confirmModulesPurge=false
    install_rc=$?
    if [ "$install_rc" -eq 0 ]; then
      if verify_pnpm_tree; then
        printf '%s' "$new_hash" > .rb-lockhash
      else
        echo "remote-runner: integrity still failing after rebuild — failing closed (suspect corrupt pnpm store on this box; wipe the store path for the missing package and rerun)" >&2
        install_rc=254
      fi
    fi
  fi
elif [ -f package-lock.json ]; then
  lockf=package-lock.json
  new_hash=$(sha256sum "$lockf" | cut -d' ' -f1)
  old_hash=$(cat .rb-lockhash 2>/dev/null || true)
  if [ "$new_hash" != "$old_hash" ] || [ ! -d node_modules ]; then
    npm ci
    install_rc=$?
    [ "$install_rc" -eq 0 ] && printf '%s' "$new_hash" > .rb-lockhash
  fi
elif [ -f bun.lock ] || [ -f bun.lockb ]; then
  lockf=bun.lock; [ -f bun.lockb ] && lockf=bun.lockb
  new_hash=$(sha256sum "$lockf" | cut -d' ' -f1)
  old_hash=$(cat .rb-lockhash 2>/dev/null || true)
  if [ "$new_hash" != "$old_hash" ] || [ ! -d node_modules ]; then
    bun install --frozen-lockfile
    install_rc=$?
    [ "$install_rc" -eq 0 ] && printf '%s' "$new_hash" > .rb-lockhash
  fi
fi
if [ "$install_rc" -ne 0 ]; then
  rc="$install_rc"
  exit "$rc"
fi

# Trigger on `playwright` invoked as a program, not on any argv substring: a test path such as
# packages/playwright/test/x.test.ts is not a playwright CLI run.
invokes_playwright=0
for arg in "$@"; do
  case "$arg" in
    playwright|*/playwright) invokes_playwright=1; break;;
  esac
done
if [ "$invokes_playwright" -eq 1 ]; then
  playwright_bin="$ws_root/node_modules/.bin/playwright"
  [ -x "$playwright_bin" ] || { echo "remote-runner: installed Playwright binary not found: $playwright_bin" >&2; rc=254; exit "$rc"; }
  "$playwright_bin" install
fi

cd "$mirror/$rel" || exit 254
# project-local bins (tsc, vitest, eslint) resolve like a pnpm-run PATH: cwd up to mirror root
d="$PWD"
while :; do
  [ -d "$d/node_modules/.bin" ] && PATH="$d/node_modules/.bin:$PATH"
  [ "$d" = "$mirror" ] && break
  d=$(dirname "$d")
done
export PATH
supervisor="${SUPERVISOR_BIN:-$HOME/.rb/command-supervisor.mjs}"
if [ ! -r "$supervisor" ]; then
  echo "remote-runner: command supervisor missing: $supervisor" >&2
  rc=254
  exit "$rc"
fi
exec node "$supervisor" \
  --mode "${SUPERVISOR_MODE:-opaque}" \
  --job-dir "$job_dir" \
  --state "$job_dir/state.json" \
  --rc "$job_dir/rc" \
  --diagnostics "$job_dir/diagnostics.json" \
  --lease-ms "${SUPERVISOR_LEASE_MS:-60000}" \
  --terminal-grace-ms "${SUPERVISOR_TERMINAL_GRACE_MS:-5000}" \
  -- "$@"
