#!/bin/bash
#
# codevibe-claude - Wrapper to run Claude Code inside tmux for mobile control
#
# This script launches Claude Code inside a tmux session, enabling:
# - Mobile prompts when screen is locked (via tmux send-keys)
# - Same user experience as regular claude command
# - Automatic session naming based on Claude session ID
#
# Usage:
#   codevibe-claude [claude-args...]
#   codevibe-claude login              # Sign in via browser
#   codevibe-claude logout             # Sign out
#   codevibe-claude status             # Show auth status
#
# Environment:
#   ENVIRONMENT                         # Set to 'production' (default) or 'development'
#
# Examples:
#   codevibe-claude                    # Start new session
#   codevibe-claude --resume           # Resume last session
#   codevibe-claude -p "fix the bug"   # Start with prompt
#   ENVIRONMENT=development codevibe-claude login  # Login to development
#

set -e

# Default to production environment if not specified
# Users can set ENVIRONMENT=development for local development
export ENVIRONMENT="${ENVIRONMENT:-production}"

# Use TMPDIR if set (macOS sets this to user-specific temp), otherwise /tmp
CODEVIBE_TMPDIR="${TMPDIR:-/tmp}"

# Get the directory where this script is located (resolving symlinks)
# This is needed because npm global installs symlink bin scripts to /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 (and similar)
# but the user's current shell hasn't sourced it yet. Subsequent
# codevibe-* runs in that same terminal then fail tmux discovery
# because /opt/homebrew/bin isn't on PATH. Prepend common locations
# so the wrapper recovers without forcing the user to open a new
# terminal. Prepend (not append) is deliberate: the Homebrew binary
# install.sh just laid down should win over any older system binary
# (e.g. a stale /usr/bin/node on Linux) at the same name. To preserve
# the relative ordering of augmented dirs, build a single prefix
# string and prepend it once — iterating prepend-per-dir would
# reverse intended order. ${PATH:+:$PATH} keeps an empty starting
# PATH from producing a trailing colon (which makes cwd searchable).
_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,
# whether SessionStart hook fired, exit code. Background curl, fail
# silently, no PII (hashed hostname + per-run random id only). Honors
# CODEVIBE_TELEMETRY_SOURCE=test for internal testing.
_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="claude"
_CV_SOURCE="${CODEVIBE_TELEMETRY_SOURCE:-production}"
_CV_STARTED_AT="$(date +%s)"
_CV_EXITED=""    # set by terminal events; suppresses trap double-fire
_CV_PLUGIN_VERSION="$(node -p "require('$PLUGIN_DIR/package.json').version" 2>/dev/null || echo unknown)"
_CV_MCP_LOG="${CODEVIBE_TMPDIR}/codevibe-claude-mcp.log"
_CV_MCP_LOG_BASELINE=0
if [ -f "$_CV_MCP_LOG" ]; then
  _CV_MCP_LOG_BASELINE=$(wc -l < "$_CV_MCP_LOG" 2>/dev/null | tr -d ' ')
  [ -z "$_CV_MCP_LOG_BASELINE" ] && _CV_MCP_LOG_BASELINE=0
fi
_CV_TMUX_STARTED="false"
_CV_AGENT_INVOKED="false"
_CV_AGENT_STARTED_AT=0
_CV_CLAUDE_EXIT_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-exit-$$"

# Strip an arbitrary string down to a JSON-safe identifier alphabet.
# Removes anything that could break the hand-built JSON payload below
# (quotes, backslashes, ANSI escapes, control bytes, tabs, newlines).
# Truncates to 40 chars to bound the impact of pathological CLI version
# output. Caller is responsible for emptiness check after sanitize.
cv_sanitize() {
  printf '%s' "$1" | LC_ALL=C tr -cd 'A-Za-z0-9._\- ' | cut -c1-40
}

# Sanitize trusted-but-still-string values that go into the payload
# (plugin version, source label) so future schema additions can't
# accidentally reintroduce a JSON-injection path.
_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 ))"
}

# Handle auth commands (login, logout, status, reset-device)
# Delegate to codevibe-core CLI (shared auth across all plugins).
# Check both the local node_modules path AND the hoisted location used
# when the plugin is installed via the @quantiya/codevibe meta-package
# (parity with the codex/gemini wrappers).
case "$1" in
    login|logout|status|reset-device)
        CORE_CLI="$PLUGIN_DIR/node_modules/@quantiya/codevibe-core/bin/codevibe.js"
        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

# Capture environment facts for the session-flow wrapper_started event.
# Each probe is non-fatal — if a CLI is missing we record "missing" rather
# than aborting; pre-flight checks below still gate execution. Every
# string that lands in the JSON payload goes through cv_sanitize so an
# agent CLI emitting ANSI escapes or quotes in `--version` can't break
# the hand-built payload.
_CV_CLAUDE_VER="missing"
command -v claude >/dev/null 2>&1 && _CV_CLAUDE_VER="$(claude --version 2>/dev/null | cv_sanitize)"
[ -z "$_CV_CLAUDE_VER" ] && _CV_CLAUDE_VER="unknown"
_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_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_CLAUDE_CREDS="false"; [ -f "$HOME/.claude/.credentials.json" ] && _CV_CLAUDE_CREDS="true"
_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\",\"claude_version\":\"$_CV_CLAUDE_VER\",\"node_version\":\"$_CV_NODE_VER\",\"tmux_version\":\"$_CV_TMUX_VER\",\"claude_credentials_present\":$_CV_CLAUDE_CREDS,\"inside_tmux\":$_CV_INSIDE_TMUX,\"is_terminal\":$_CV_IS_TTY"

# Check authentication before launching. Same dual-path lookup as the
# auth case above so the hoisted meta-package install location works.
CORE_CLI="$PLUGIN_DIR/node_modules/@quantiya/codevibe-core/bin/codevibe.js"
if [ ! -f "$CORE_CLI" ]; then
    CORE_CLI="$PLUGIN_DIR/../codevibe-core/bin/codevibe.js"
fi
if [ -f "$CORE_CLI" ]; then
    AUTH_STATUS=$(node "$CORE_CLI" status 2>/dev/null)
    if echo "$AUTH_STATUS" | grep -q "Not authenticated"; then
        echo ""
        echo "⚠️  CodeVibe: Not authenticated."
        echo "   Run 'codevibe-claude login' to sign in first."
        echo ""
        cv_failed "codevibe_not_authenticated"
        sleep 1
        exit 1
    fi
fi

# Configuration
TMUX_SESSION_PREFIX="codevibe-claude"
LOG_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-wrapper.log"

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

# Check if tmux is installed
if ! command -v tmux &> /dev/null; then
    echo "Error: tmux is required but not installed."
    echo "Install with: brew install tmux"
    cv_failed "tmux_missing"
    sleep 1
    exit 1
fi

# Check if claude CLI is installed.
# The meta-package wrapper (codevibe/bin/codevibe-claude) does this same
# check at a higher layer, but a user invoking the plugin wrapper
# directly (e.g., when developing locally) will hit this fallback. Without
# it, missing claude becomes an inner-tmux exit-127 which leaves the
# diagnostic ambiguity that wrapper telemetry is meant to remove.
if ! command -v claude &> /dev/null; then
    echo "Error: claude CLI is not installed."
    echo "Install Claude Code from https://docs.anthropic.com/claude/docs/claude-code"
    cv_failed "claude_missing"
    sleep 1
    exit 1
fi

# Check if the plugin's MCP server bundle is built. The daemon is
# launched by Claude Code's SessionStart hook, not by this wrapper, but
# if dist/server.js is missing the hook will silently fail and "no
# SessionStart hook fired" will be misattributed to claude itself.
if [ ! -f "$PLUGIN_DIR/dist/server.js" ]; then
    echo "Error: MCP server bundle not built (missing $PLUGIN_DIR/dist/server.js)."
    echo "Run 'npm run build' in the plugin directory, or reinstall:"
    echo "  npm install -g @quantiya/codevibe"
    cv_failed "server_not_built"
    sleep 1
    exit 1
fi

# Cleanup function for telemetry — Claude has no server lifecycle in
# the wrapper (daemon is managed by Claude Code's SessionStart hook),
# so this trap exists purely to fire wrapper_exited on EXIT and to
# remove the per-run exit-code file.
cleanup() {
    local wrapper_exit_code=$?

    if [ -z "$_CV_EXITED" ]; then
        _CV_EXITED="exited"
        local claude_exit="unknown"
        if [ -f "$_CV_CLAUDE_EXIT_FILE" ]; then
            claude_exit="$(cat "$_CV_CLAUDE_EXIT_FILE" 2>/dev/null | head -c 10 | tr -d '\n\r ')"
            [ -z "$claude_exit" ] && claude_exit="unknown"
        fi
        local lifetime=$(( $(date +%s) - _CV_STARTED_AT ))
        local claude_lifetime=0
        if [ "$_CV_AGENT_STARTED_AT" -gt 0 ] 2>/dev/null; then
            claude_lifetime=$(( $(date +%s) - _CV_AGENT_STARTED_AT ))
        fi
        local hook_fired="false"
        if [ -f "$_CV_MCP_LOG" ]; then
            if tail -n "+$((_CV_MCP_LOG_BASELINE + 1))" "$_CV_MCP_LOG" 2>/dev/null \
                | grep -q "SessionStart" 2>/dev/null; then
                hook_fired="true"
            fi
        fi
        # Outcome priority: SIGINT/SIGTERM beats everything (user intent).
        # Then "we never got far enough to invoke claude" — distinct from
        # "we invoked claude via passthrough but never started a tmux of
        # our own" (the latter is a normal direct-run, not an abort).
        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 [ "$claude_exit" != "unknown" ] && [ "$claude_exit" != "0" ]; then
            outcome="error_exit"
        elif [ "$claude_lifetime" -lt 5 ] 2>/dev/null; then
            outcome="early_exit"
        elif [ "$claude_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,\"claude_exit_code\":\"$claude_exit\",\"claude_lifetime_seconds\":$claude_lifetime,\"tmux_session_started\":$_CV_TMUX_STARTED,\"agent_invoked\":$_CV_AGENT_INVOKED,\"session_start_hook_fired\":$hook_fired,\"terminal_outcome\":\"$outcome\""
    fi

    rm -f "$_CV_CLAUDE_EXIT_FILE"
}

trap cleanup EXIT INT TERM

# Generate a unique session name
SESSION_NAME="${TMUX_SESSION_PREFIX}-$$"

log "Starting codevibe-claude with session: $SESSION_NAME"
log "Arguments: $*"

# Check if we're already inside tmux.
# We deliberately do NOT `exec` here — running claude as a child process
# lets the EXIT trap fire after it returns so wrapper_exited still gets
# emitted on these direct-run paths. Behaviorally identical for the user
# (claude remains the foreground process for the duration).
if [ -n "$TMUX" ]; then
    log "Already inside tmux, running claude directly"
    _CV_AGENT_INVOKED="true"
    _CV_AGENT_STARTED_AT="$(date +%s)"
    # `|| _CV_RC=$?` is load-bearing: with `set -e`, a non-zero exit
    # from claude would abort the wrapper before we capture the exit
    # code, leaving wrapper_exited with claude_exit_code="unknown". The
    # `||` form catches non-zero without triggering set -e, while exit
    # 0 leaves _CV_RC at its 0 default. printf's `|| true` keeps a
    # disk-full failure from clobbering diagnostics.
    _CV_RC=0
    claude "$@" || _CV_RC=$?
    printf '%s' "$_CV_RC" > "$_CV_CLAUDE_EXIT_FILE" 2>/dev/null || true
    exit "$_CV_RC"
fi

# Check if running in a terminal — same direct-run treatment as above.
if [ ! -t 0 ] || [ ! -t 1 ]; then
    log "Not running in a terminal, running claude directly"
    _CV_AGENT_INVOKED="true"
    _CV_AGENT_STARTED_AT="$(date +%s)"
    _CV_RC=0
    claude "$@" || _CV_RC=$?
    printf '%s' "$_CV_RC" > "$_CV_CLAUDE_EXIT_FILE" 2>/dev/null || true
    exit "$_CV_RC"
fi

# Create tmux session and run claude
# -d: detached initially so we can set up
# Then attach to it
log "Creating tmux session: $SESSION_NAME"

# Build the claude command with proper escaping
CLAUDE_CMD="claude"
for arg in "$@"; do
    # Escape single quotes in arguments
    escaped_arg=$(printf '%s' "$arg" | sed "s/'/'\\\\''/g")
    CLAUDE_CMD="$CLAUDE_CMD '$escaped_arg'"
done

# Create the session running claude, then attach
# We use a wrapper that:
# 1. Exports the session name so hooks can find it
# 2. Runs claude
# 3. Exits the tmux session when claude exits

tmux new-session -d -s "$SESSION_NAME" -x "$(tput cols)" -y "$(tput lines)" \
    "export CODEVIBE_TMUX_SESSION='$SESSION_NAME'; export ENVIRONMENT='$ENVIRONMENT'; $CLAUDE_CMD; printf '%s' \"\$?\" > '$_CV_CLAUDE_EXIT_FILE'; exit"
_CV_TMUX_STARTED="true"
_CV_AGENT_INVOKED="true"
_CV_AGENT_STARTED_AT="$(date +%s)"

# Enable mouse support for scrolling
tmux set-option -t "$SESSION_NAME" -g mouse on

# Enable copy/paste with system clipboard (macOS: pbcopy, WSL: clip.exe, Linux: xclip)
tmux set-option -t "$SESSION_NAME" set-clipboard on
tmux set-window-option -t "$SESSION_NAME" mode-keys vi
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"
  tmux bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "$CLIP_CMD"
fi

# Store session mapping for the MCP server to find
# The session-start hook will update this with the actual Claude session ID
echo "$SESSION_NAME" > "${CODEVIBE_TMPDIR}/codevibe-claude-tmux-session-$$"

log "Attaching to tmux session: $SESSION_NAME"

# Attach to the session.
# Note: we deliberately do NOT `exec` tmux here — letting bash continue
# means the EXIT trap fires after detach, which is how the wrapper_exited
# telemetry event gets recorded. The behavioral difference is invisible
# to the user (tmux attach is still the foreground command).
tmux attach-session -t "$SESSION_NAME"
