#!/bin/bash
#
# codevibe-agy — wrapper that runs Antigravity CLI (agy) inside a tmux
# session bridged to the CodeVibe MCP server for mobile sync.
#
# Architecture (DESIGN.md §3 + §5.2):
#
#   1. ensureInstalled (idempotent): writes plugin manifest to
#      ~/.gemini/antigravity-cli/plugins/codevibe-antigravity/ + wires
#      ~/.gemini/config/mcp_config.json's mcpServers entry.
#
#   2. Generate ephemeral port + bearer token; create runtime dir at
#      ~/.gemini/antigravity-cli/plugins/codevibe-antigravity/runtime/$$
#      with mode 0700.
#
#   3. Create tmux session `codevibe-agy-$$` FIRST. Inside the tmux
#      session, a holding loop polls `<runtime-dir>/conn.json` (the
#      MCP server's atomic post-listen writepoint) for up to 10s, then
#      execs agy. This ordering means TmuxPaneObserver.start() can
#      always `tmux pipe-pane -t $SESSION_NAME` successfully.
#
#   4. Spawn MCP server (`node dist/server.js --port ... --runtime-dir ...`)
#      in the background. Bearer token via CODEVIBE_AGY_MCP_TOKEN env
#      (NOT --token argv — keeps it out of `ps -ef`). Server's
#      HttpApi.writePortFile writes conn.json atomically once listen()
#      resolves, which unblocks the inner script's holding loop.
#      Also exports CODEVIBE_AGY_MCP_URL / CODEVIBE_AGY_TMUX_TARGET /
#      CODEVIBE_AGY_WRAPPER_PID.
#
#   5. Attach user to the tmux session. Poll `tmux has-session` after
#      attach returns (handles Ctrl+B d detach without killing MCP).
#
#   6. EXIT trap: mark backend session INACTIVE (delegated to MCP
#      server's signal handler), kill MCP server, remove runtime dir.
#
# Usage:
#   codevibe-agy [agy-args...]         # Start agy session
#   codevibe-agy login                 # Sign in via browser
#   codevibe-agy logout                # Sign out
#   codevibe-agy status                # Show auth status
#   codevibe-agy reset-device          # Reset device key (re-pair)
#
# Environment:
#   ENVIRONMENT                    Set to 'production' (default) or 'development'
#   AGY_PATH                       Path to agy binary (default: $(command -v agy))
#
# v1.0 limitations:
#   - Requires tmux. Print mode (`--print` / `-p`) is NOT supported.
#   - Requires Node.js ≥ 18 for the MCP server.
#

set -e

# Whitelist ENVIRONMENT so a malicious env value can't break out of the
# single-quoted shell context where it's later interpolated into the tmux
# inner command. Only `production` and `development` are legitimate; any
# other value is silently rejected back to `production`. (Stage 1 HIGH
# finding 2026-05-20.)
case "${ENVIRONMENT:-}" in
    production|development) export ENVIRONMENT="${ENVIRONMENT}" ;;
    *) export ENVIRONMENT="production" ;;
esac
CODEVIBE_TMPDIR="${TMPDIR:-/tmp}"

# Resolve plugin dir via symlink-aware traversal (npm globals symlink
# bin/ into /usr/local/bin/).
SOURCE="${BASH_SOURCE[0]}"
while [ -L "$SOURCE" ]; do
  DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
PLUGIN_DIR="$(dirname "$SCRIPT_DIR")"

# ─── PATH augmentation ───────────────────────────────────────────────
# When the install one-liner runs in a fresh terminal, Homebrew's
# installer writes the shellenv eval into ~/.zprofile (etc.) but the
# user's current shell hasn't sourced it yet. Subsequent codevibe-*
# runs in that same terminal then fail tmux/node/agy discovery because
# /opt/homebrew/bin isn't on PATH. Prepend common locations so the
# wrapper recovers without forcing a new terminal.
_CV_NEW_PATHS=""
for _CV_DIR in /opt/homebrew/bin /opt/homebrew/sbin /usr/local/bin /usr/local/sbin /opt/local/bin /usr/bin /bin; do
  case ":$PATH:" in
    *":$_CV_DIR:"*) ;;
    *) [ -d "$_CV_DIR" ] && _CV_NEW_PATHS="$_CV_NEW_PATHS:$_CV_DIR" ;;
  esac
done
[ -n "$_CV_NEW_PATHS" ] && export PATH="${_CV_NEW_PATHS#:}${PATH:+:$PATH}"
unset _CV_DIR _CV_NEW_PATHS

# ─── Wrapper telemetry (GA4 Measurement Protocol) ─────────────────────
# Diagnoses agent CLI failures: pre-flight bailouts, fast-die patterns,
# exit code. Background curl, fail silently, no PII (hashed hostname +
# per-run random id only). Honors CODEVIBE_TELEMETRY_SOURCE=test.
_CV_MID="G-GS74YEQTB8"
_CV_SEC="lAfOF6OxRzSQ-NsLBRjhAg"
_CV_CID="$(echo "$(uname -n)-$(id -u)" | (sha256sum 2>/dev/null || shasum -a 256 2>/dev/null || echo "anonymous-fallback ") | cut -c1-36)"
_CV_RUN_ID="$(head -c 16 /dev/urandom 2>/dev/null | od -An -tx1 | tr -d ' \n' | cut -c1-32)"
[ -z "$_CV_RUN_ID" ] && _CV_RUN_ID="fallback-$(date +%s)-$$"
_CV_AGENT="antigravity"
_CV_SOURCE="${CODEVIBE_TELEMETRY_SOURCE:-production}"
_CV_STARTED_AT="$(date +%s)"
_CV_EXITED=""
_CV_PLUGIN_VERSION="$(node -p "require('$PLUGIN_DIR/package.json').version" 2>/dev/null || echo unknown)"
_CV_MCP_LOG="${CODEVIBE_TMPDIR}/codevibe-agy-mcp.log"
_CV_TMUX_STARTED="false"
_CV_AGENT_INVOKED="false"
_CV_AGENT_STARTED_AT=0
_CV_AGY_EXIT_FILE="${CODEVIBE_TMPDIR}/codevibe-agy-exit-$$"

# Sanitize values that go into the GA4 JSON payload so a CLI emitting
# ANSI escapes or quotes in `--version` can't break the hand-built JSON.
# Output capped at 40 chars. Caller checks emptiness afterward.
cv_sanitize() {
  printf '%s' "$1" | LC_ALL=C tr -cd 'A-Za-z0-9._\- ' | cut -c1-40
}
_CV_PLUGIN_VERSION="$(cv_sanitize "$_CV_PLUGIN_VERSION")"
[ -z "$_CV_PLUGIN_VERSION" ] && _CV_PLUGIN_VERSION="unknown"
_CV_SOURCE="$(cv_sanitize "$_CV_SOURCE")"
[ -z "$_CV_SOURCE" ] && _CV_SOURCE="production"

cv_telem() {
  local event="$1"; shift
  local params="$*"
  curl -s -X POST \
    "https://www.google-analytics.com/mp/collect?measurement_id=${_CV_MID}&api_secret=${_CV_SEC}" \
    -H "Content-Type: application/json" \
    -d "{\"client_id\":\"${_CV_CID}\",\"events\":[{\"name\":\"${event}\",\"params\":{\"agent\":\"${_CV_AGENT}\",\"plugin_version\":\"${_CV_PLUGIN_VERSION}\",\"source\":\"${_CV_SOURCE}\",\"run_id\":\"${_CV_RUN_ID}\"${params:+,$params}}}]}" \
    </dev/null >/dev/null 2>&1 &
}

cv_failed() {
  [ -n "$_CV_EXITED" ] && return 0
  _CV_EXITED="failed"
  cv_telem "wrapper_failed" "\"reason\":\"$1\",\"lifetime_seconds\":$(( $(date +%s) - _CV_STARTED_AT ))"
}

# ─── Auth commands: delegate to shared codevibe-core CLI ───────────────
case "$1" in
    login|logout|status|reset-device)
        CORE_CLI="$PLUGIN_DIR/node_modules/@quantiya/codevibe-core/bin/codevibe.js"
        # Also check hoisted location (when installed via @quantiya/codevibe meta-package).
        if [ ! -f "$CORE_CLI" ]; then
            CORE_CLI="$PLUGIN_DIR/../codevibe-core/bin/codevibe.js"
        fi
        if [ -f "$CORE_CLI" ]; then
            cv_telem "wrapper_started" "\"invocation\":\"auth_$1\",\"os\":\"$(uname -s | cv_sanitize)\",\"arch\":\"$(uname -m | cv_sanitize)\""
            exec node "$CORE_CLI" "$1"
        else
            echo "Error: codevibe-core not found. Try reinstalling: npm install -g @quantiya/codevibe"
            cv_failed "core_not_found"
            sleep 1
            exit 1
        fi
        ;;
esac

# ─── Environment probes ───────────────────────────────────────────────
_CV_NODE_VER="missing"
command -v node >/dev/null 2>&1 && _CV_NODE_VER="$(node -v 2>/dev/null | cv_sanitize)"
[ -z "$_CV_NODE_VER" ] && _CV_NODE_VER="unknown"
_CV_TMUX_VER="missing"
command -v tmux >/dev/null 2>&1 && _CV_TMUX_VER="$(tmux -V 2>/dev/null | cv_sanitize)"
[ -z "$_CV_TMUX_VER" ] && _CV_TMUX_VER="unknown"
_CV_AGY_BIN="${AGY_PATH:-$(command -v agy 2>/dev/null || true)}"
_CV_AGY_VER="missing"
[ -n "$_CV_AGY_BIN" ] && _CV_AGY_VER="$("$_CV_AGY_BIN" --version 2>/dev/null | cv_sanitize)"
[ -z "$_CV_AGY_VER" ] && _CV_AGY_VER="unknown"
_CV_OS_VER="$(uname -s | cv_sanitize)"
[ -z "$_CV_OS_VER" ] && _CV_OS_VER="unknown"
_CV_ARCH_VER="$(uname -m | cv_sanitize)"
[ -z "$_CV_ARCH_VER" ] && _CV_ARCH_VER="unknown"
_CV_INSIDE_TMUX="false"; [ -n "$TMUX" ] && _CV_INSIDE_TMUX="true"
_CV_IS_TTY="false"; { [ -t 0 ] && [ -t 1 ]; } && _CV_IS_TTY="true"
cv_telem "wrapper_started" "\"invocation\":\"session\",\"os\":\"$_CV_OS_VER\",\"arch\":\"$_CV_ARCH_VER\",\"agy_version\":\"$_CV_AGY_VER\",\"node_version\":\"$_CV_NODE_VER\",\"tmux_version\":\"$_CV_TMUX_VER\",\"inside_tmux\":$_CV_INSIDE_TMUX,\"is_terminal\":$_CV_IS_TTY"

LOG_FILE="${CODEVIBE_TMPDIR}/codevibe-agy-wrapper.log"
MCP_LOG_FILE="${CODEVIBE_TMPDIR}/codevibe-agy-mcp.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}

# ─── Reject unsupported invocations ───────────────────────────────────
# `--print` / `-p` mode bypasses the interactive TUI we depend on. Bail
# out with a clear message rather than silently producing no mobile sync.
# (DESIGN.md §5.2.)
for arg in "$@"; do
    case "$arg" in
        --print|-p|--prompt|--print=*|--prompt=*|-p=*)
            echo "Error: codevibe-agy does not support --print / -p / --prompt in v1.0."
            echo "Run \"agy ...\" directly for non-interactive use; mobile sync is interactive-only."
            cv_failed "print_mode_unsupported"
            sleep 1
            exit 1
            ;;
    esac
done

# ─── Pre-flight checks ────────────────────────────────────────────────
if ! command -v tmux &> /dev/null; then
    echo "Error: tmux is required but not installed."
    echo "Install with: brew install tmux  (macOS)  or  apt-get install tmux  (Debian/Ubuntu)"
    cv_failed "tmux_missing"
    sleep 1
    exit 1
fi

if [ -z "$_CV_AGY_BIN" ] || ! [ -x "$_CV_AGY_BIN" ]; then
    echo "Error: Antigravity CLI (\`agy\`) is not installed or not executable."
    echo "Install instructions: https://antigravity.google/docs"
    cv_failed "agy_missing"
    sleep 1
    exit 1
fi

if ! command -v node &> /dev/null; then
    echo "Error: Node.js is required but not installed."
    echo "Install Node.js 18+ from: https://nodejs.org"
    cv_failed "node_missing"
    sleep 1
    exit 1
fi

if [ ! -f "$PLUGIN_DIR/dist/server.js" ]; then
    echo "Error: MCP server not built. Run 'npm run build' in the plugin directory first."
    cv_failed "server_not_built"
    sleep 1
    exit 1
fi

if [ ! -f "$PLUGIN_DIR/dist/installer-cli.js" ]; then
    echo "Error: Installer CLI not built. Run 'npm run build' in the plugin directory first."
    cv_failed "installer_not_built"
    sleep 1
    exit 1
fi

# ─── Atomic install of plugin footprint (DESIGN.md §5.1) ───────────────
# Runs ensureInstalled which writes antigravity-plugin.json to
# ~/.gemini/antigravity-cli/plugins/codevibe-antigravity/ + adds the
# mcpServers entry to ~/.gemini/config/mcp_config.json. Idempotent —
# subsequent runs are no-ops if the manifest hash matches.
log "Running ensureInstalled"
if ! node "$PLUGIN_DIR/dist/installer-cli.js"; then
    echo "Error: codevibe-antigravity installer failed."
    cv_failed "installer_failed"
    sleep 1
    exit 1
fi

# ─── Token + port + runtime dir (DESIGN.md §4.5) ──────────────────────
WRAPPER_PID="$$"
SESSION_NAME="codevibe-agy-${WRAPPER_PID}"

# Bearer token: 48-char hex (24 bytes / 192 bits entropy) from
# /dev/urandom, fallback to date+pid. Passed to the MCP server via the
# CODEVIBE_AGY_MCP_TOKEN env var (NOT command-line argv) so it's not
# visible to other local processes via `ps -ef` / /proc/<pid>/cmdline.
# (Stage 1 HIGH finding 2026-05-20.)
BEARER_TOKEN="$(head -c 24 /dev/urandom 2>/dev/null | od -An -tx1 | tr -d ' \n' | cut -c1-48)"
[ -z "$BEARER_TOKEN" ] && BEARER_TOKEN="fallback-$(date +%s%N | cv_sanitize)-${WRAPPER_PID}"

# Ephemeral port: ask Node to pick a free one (binds + closes). This is
# inherently TOCTOU (port may be claimed between pick + bind by the MCP
# server). If that race fires, the MCP server dies on bind; we detect
# via the post-spawn `kill -0` check below and surface the tail of
# $MCP_LOG_FILE for diagnostics. Node prints the port to stdout for
# capture here.
MCP_PORT="$(node -e '
const net = require("net");
const srv = net.createServer();
srv.unref();
srv.listen(0, "127.0.0.1", () => {
  const a = srv.address();
  if (a && typeof a === "object") console.log(a.port);
  srv.close();
});
' 2>/dev/null)"
if ! [[ "$MCP_PORT" =~ ^[0-9]+$ ]]; then
    echo "Error: failed to pick an ephemeral port for the MCP server."
    cv_failed "port_pick_failed"
    sleep 1
    exit 1
fi

# Runtime dir at ~/.gemini/antigravity-cli/plugins/codevibe-antigravity/runtime/$$
# with mode 0700. mkdir -p first, then chmod (umask may have made it 0755).
RUNTIME_BASE="$HOME/.gemini/antigravity-cli/plugins/codevibe-antigravity/runtime"
RUNTIME_DIR="${RUNTIME_BASE}/${WRAPPER_PID}"
mkdir -p "$RUNTIME_DIR"
chmod 0700 "$RUNTIME_DIR" 2>/dev/null || true
# Also tighten the runtime base — best-effort, no fatal on failure.
chmod 0700 "$RUNTIME_BASE" 2>/dev/null || true

log "Wrapper PID=$WRAPPER_PID; token=$(printf '%s' "$BEARER_TOKEN" | cut -c1-8)...; port=$MCP_PORT; runtime=$RUNTIME_DIR"

# Export env vars consumed by the MCP server + any in-tree tooling.
export CODEVIBE_AGY_MCP_TOKEN="$BEARER_TOKEN"
export CODEVIBE_AGY_MCP_URL="http://127.0.0.1:${MCP_PORT}"
export CODEVIBE_AGY_TMUX_TARGET="$SESSION_NAME"
export CODEVIBE_AGY_WRAPPER_PID="$WRAPPER_PID"

# ─── Cleanup trap ─────────────────────────────────────────────────────
cleanup() {
    local wrapper_exit_code=$?
    log "Cleanup triggered"

    # Fire wrapper_exited telemetry BEFORE killing the server so MCP
    # logs are intact. cv_failed sets _CV_EXITED on pre-flight failures,
    # so this block won't double-fire.
    if [ -z "$_CV_EXITED" ]; then
        _CV_EXITED="exited"
        local agy_exit="unknown"
        if [ -f "$_CV_AGY_EXIT_FILE" ]; then
            agy_exit="$(cat "$_CV_AGY_EXIT_FILE" 2>/dev/null | head -c 10 | tr -d '\n\r ')"
            [ -z "$agy_exit" ] && agy_exit="unknown"
        fi
        local lifetime=$(( $(date +%s) - _CV_STARTED_AT ))
        local agy_lifetime=0
        if [ "$_CV_AGENT_STARTED_AT" -gt 0 ] 2>/dev/null; then
            agy_lifetime=$(( $(date +%s) - _CV_AGENT_STARTED_AT ))
        fi
        local outcome
        if [ "$wrapper_exit_code" = "130" ] || [ "$wrapper_exit_code" = "143" ]; then
            outcome="interrupted"
        elif [ "$_CV_AGENT_INVOKED" = "false" ]; then
            outcome="pre_invoke_abort"
        elif [ "$agy_exit" != "unknown" ] && [ "$agy_exit" != "0" ]; then
            outcome="error_exit"
        elif [ "$agy_lifetime" -lt 5 ] 2>/dev/null; then
            outcome="early_exit"
        elif [ "$agy_lifetime" -lt 60 ] 2>/dev/null; then
            outcome="clean_short"
        else
            outcome="clean_long"
        fi
        cv_telem "wrapper_exited" "\"exit_code\":$wrapper_exit_code,\"lifetime_seconds\":$lifetime,\"agy_exit_code\":\"$agy_exit\",\"agy_lifetime_seconds\":$agy_lifetime,\"tmux_session_started\":$_CV_TMUX_STARTED,\"agent_invoked\":$_CV_AGENT_INVOKED,\"terminal_outcome\":\"$outcome\""
    fi

    # Stop the MCP server — graceful first, then SIGKILL fallback.
    # The server's SIGTERM handler marks the session INACTIVE.
    if [ -n "$MCP_PID" ] && kill -0 "$MCP_PID" 2>/dev/null; then
        log "Stopping MCP server (PID: $MCP_PID)"
        kill -TERM "$MCP_PID" 2>/dev/null || true
        # Wait up to 3s for graceful shutdown.
        local i=0
        while [ $i -lt 30 ] && kill -0 "$MCP_PID" 2>/dev/null; do
            sleep 0.1
            i=$((i + 1))
        done
        if kill -0 "$MCP_PID" 2>/dev/null; then
            log "MCP server did not exit cleanly; sending SIGKILL"
            kill -KILL "$MCP_PID" 2>/dev/null || true
        fi
        wait "$MCP_PID" 2>/dev/null || true
    fi

    # Best-effort cleanup of per-wrapper runtime dir.
    if [ -n "$RUNTIME_DIR" ] && [ -d "$RUNTIME_DIR" ]; then
        rm -rf "$RUNTIME_DIR" 2>/dev/null || true
    fi
    # PID + exit files.
    rm -f "${CODEVIBE_TMPDIR}/codevibe-agy-mcp-${WRAPPER_PID}.pid"
    rm -f "$_CV_AGY_EXIT_FILE"
}
trap cleanup EXIT INT TERM

# ─── Direct-run paths (already-inside-tmux + non-TTY) ─────────────────
# In these cases we skip the MCP-server + tmux orchestration and just
# run agy directly. Mobile sync is not available but the user can still
# use agy.

if [ -n "$TMUX" ]; then
    log "Already inside tmux — running agy directly (no mobile sync)"
    _CV_AGENT_INVOKED="true"
    _CV_AGENT_STARTED_AT="$(date +%s)"
    _CV_RC=0
    "$_CV_AGY_BIN" "$@" || _CV_RC=$?
    printf '%s' "$_CV_RC" > "$_CV_AGY_EXIT_FILE" 2>/dev/null || true
    exit "$_CV_RC"
fi

if [ ! -t 0 ] || [ ! -t 1 ]; then
    log "Not running in a terminal — running agy directly (no mobile sync)"
    _CV_AGENT_INVOKED="true"
    _CV_AGENT_STARTED_AT="$(date +%s)"
    _CV_RC=0
    "$_CV_AGY_BIN" "$@" || _CV_RC=$?
    printf '%s' "$_CV_RC" > "$_CV_AGY_EXIT_FILE" 2>/dev/null || true
    exit "$_CV_RC"
fi

# ─── tmux session + agy exec ──────────────────────────────────────────
# Ordering: tmux session is created BEFORE the MCP server spawn so that
# the server's TmuxPaneObserver can successfully `tmux pipe-pane -t
# $SESSION_NAME` on startup. Previously, MCP spawned first and could
# die on a `pipe-pane: can't find session` error if the wrapper hadn't
# yet created the session. (Codex Stage 2 HIGH finding 2026-05-20.)
#
# Inner-script readiness wait: agy itself doesn't launch until the
# MCP server's conn.json exists (signal that auth + HTTP bind both
# succeeded). The MCP server writes conn.json from HttpApi.start()
# atomically after listen() returns, so its presence is a reliable
# "ready" indicator. We poll every 100ms up to 10s; on timeout we run
# agy anyway (degraded mode — no mobile sync) so the user isn't stuck
# at a blank pane. (Codex Stage 2 HIGH finding 2026-05-20.)
log "Creating tmux session: $SESSION_NAME"

# Single-quote-escape every user-controllable scalar that gets
# interpolated into the tmux inner single-quoted context. Apply the
# standard 'foo'\''bar' trick so an AGY_PATH or TMPDIR (both user-
# settable env vars) containing a single quote can't break out of the
# quoted context. (Stage 1 HIGH 2026-05-20.)
escape_sq() { printf '%s' "$1" | sed "s/'/'\\\\''/g"; }

escaped_bin="$(escape_sq "$_CV_AGY_BIN")"
escaped_exit_file="$(escape_sq "$_CV_AGY_EXIT_FILE")"
escaped_conn_file="$(escape_sq "$RUNTIME_DIR/conn.json")"
escaped_mcp_log="$(escape_sq "$MCP_LOG_FILE")"
AGY_CMD="'$escaped_bin'"
for arg in "$@"; do
    escaped_arg="$(escape_sq "$arg")"
    AGY_CMD="$AGY_CMD '$escaped_arg'"
done

# Inner script: wait for MCP readiness → run agy → record exit code.
# tmux inherits our exported env (CODEVIBE_AGY_MCP_TOKEN etc.) so no
# secrets in the inner argv.
INNER_SCRIPT="
i=0
while [ \$i -lt 100 ] && [ ! -f '$escaped_conn_file' ]; do
  sleep 0.1
  i=\$((i + 1))
done
if [ ! -f '$escaped_conn_file' ]; then
  echo 'codevibe-agy: MCP server not ready after 10s; running agy without mobile sync.' >&2
  echo 'codevibe-agy: see $escaped_mcp_log for details.' >&2
fi
export ENVIRONMENT='$ENVIRONMENT'
$AGY_CMD
printf '%s' \"\$?\" > '$escaped_exit_file'
exit"

tmux new-session -d -s "$SESSION_NAME" -x "$(tput cols)" -y "$(tput lines)" "$INNER_SCRIPT"
_CV_TMUX_STARTED="true"
_CV_AGENT_INVOKED="true"
_CV_AGENT_STARTED_AT="$(date +%s)"

# Quality-of-life: mouse scrolling + clipboard integration. These are
# optional — wrap in `|| true` so a failure on an exotic tmux build
# doesn't kill the wrapper (which would orphan agy + MCP). (Codex
# Stage 2 MED finding 2026-05-20.)
tmux set-option -t "$SESSION_NAME" -g mouse on 2>/dev/null || true
tmux set-option -t "$SESSION_NAME" set-clipboard on 2>/dev/null || true
tmux set-window-option -t "$SESSION_NAME" mode-keys vi 2>/dev/null || true
if command -v pbcopy >/dev/null 2>&1; then
  CLIP_CMD="pbcopy"
elif grep -qi microsoft /proc/sys/kernel/osrelease 2>/dev/null && command -v clip.exe >/dev/null 2>&1; then
  CLIP_CMD="clip.exe"
elif command -v wl-copy >/dev/null 2>&1; then
  CLIP_CMD="wl-copy"
elif command -v xclip >/dev/null 2>&1; then
  CLIP_CMD="xclip -selection clipboard"
fi
if [ -n "${CLIP_CMD:-}" ]; then
  tmux bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "$CLIP_CMD" 2>/dev/null || true
  tmux bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "$CLIP_CMD" 2>/dev/null || true
fi

# ─── Spawn MCP server (background) ────────────────────────────────────
# Token passed via $CODEVIBE_AGY_MCP_TOKEN env (already exported above),
# NOT via --token argv — argv is visible to other local processes via
# `ps -ef` / /proc/<pid>/cmdline. (Stage 1 HIGH 2026-05-20.)
log "Starting MCP server on port $MCP_PORT"
node "$PLUGIN_DIR/dist/server.js" \
    --port "$MCP_PORT" \
    --runtime-dir "$RUNTIME_DIR" \
    >> "$MCP_LOG_FILE" 2>&1 &
MCP_PID=$!
echo "$MCP_PID" > "${CODEVIBE_TMPDIR}/codevibe-agy-mcp-${WRAPPER_PID}.pid"
log "MCP server PID: $MCP_PID"

# Sanity check: did the process at least survive the spawn? If it died
# already, surface diagnostics. Note this is a coarse check — the inner
# tmux script polls conn.json for the real readiness signal so a slow-
# but-eventually-up server is fine here. (Codex Stage 2 HIGH 2026-05-20.)
sleep 1
if ! kill -0 "$MCP_PID" 2>/dev/null; then
    log "MCP server died on startup"
    echo ""
    tail -3 "$MCP_LOG_FILE" 2>/dev/null | grep -v '^\[' | head -3
    echo ""
    echo "Server failed to start. Check $MCP_LOG_FILE for details."
    echo "Common causes: not signed in (\`codevibe-agy login\`), port in use, ENV mismatch."
    # tmux session is already created — destroy it so the user doesn't
    # attach to a degraded session that will time-out waiting for conn.json.
    tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
    cv_failed "server_died_on_startup"
    sleep 1
    exit 1
fi

log "Attaching to tmux session: $SESSION_NAME"

# Attach to the session. When the user detaches (Ctrl+B d) `attach-session`
# returns BUT the underlying session keeps running — we then poll until
# the inner agy command finishes (session disappears) so MCP stays alive
# for the user to re-attach. Without this poll, detach would trigger the
# EXIT trap and kill MCP behind the user's back. (Codex Stage 2 MED 2026-05-20.)
tmux attach-session -t "$SESSION_NAME" || true
while tmux has-session -t "$SESSION_NAME" 2>/dev/null; do
    sleep 1
done

# Session is gone — agy exited. Cleanup is handled by the trap.
