#!/bin/bash
#
# codevibe-gemini - Wrapper to run Gemini CLI inside tmux for mobile control
#
# This script launches Gemini CLI inside a tmux session, enabling:
# - Mobile prompts when screen is locked (via tmux send-keys)
# - Same user experience as regular gemini command
# - Automatic sync with iOS app via MCP server
#
# Usage:
#   codevibe-gemini [gemini-args...]
#   codevibe-gemini login              # Sign in via browser
#   codevibe-gemini logout             # Sign out
#   codevibe-gemini status             # Show auth status
#
# Environment:
#   ENVIRONMENT                         # Set to 'production' (default) or 'development'
#
# Examples:
#   codevibe-gemini                    # Start new session
#   codevibe-gemini --resume           # Resume last session
#   codevibe-gemini "fix the bug"      # Start with prompt
#   ENVIRONMENT=development codevibe-gemini 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="gemini"
_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-gemini-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_GEMINI_EXIT_FILE="${CODEVIBE_TMPDIR}/codevibe-gemini-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)
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

# 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_GEMINI_VER="missing"
command -v gemini >/dev/null 2>&1 && _CV_GEMINI_VER="$(gemini --version 2>/dev/null | cv_sanitize)"
[ -z "$_CV_GEMINI_VER" ] && _CV_GEMINI_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_GEMINI_AUTH="false"; [ -f "$HOME/.gemini/oauth_creds.json" ] && _CV_GEMINI_AUTH="true"
_CV_GEMINI_SETTINGS="false"; [ -f "$HOME/.gemini/settings.json" ] && _CV_GEMINI_SETTINGS="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\",\"gemini_version\":\"$_CV_GEMINI_VER\",\"node_version\":\"$_CV_NODE_VER\",\"tmux_version\":\"$_CV_TMUX_VER\",\"gemini_auth_present\":$_CV_GEMINI_AUTH,\"gemini_settings_present\":$_CV_GEMINI_SETTINGS,\"inside_tmux\":$_CV_INSIDE_TMUX,\"is_terminal\":$_CV_IS_TTY"

# Export hooks directory for hook scripts to use
export CODEVIBE_HOOKS_DIR="$PLUGIN_DIR/hooks"

# Auto-configure hooks on startup if not already configured
configure_hooks_if_needed() {
    GEMINI_SETTINGS_DIR="$HOME/.gemini"
    GEMINI_SETTINGS_FILE="$GEMINI_SETTINGS_DIR/settings.json"
    HOOKS_DIR="$PLUGIN_DIR/hooks"
    TEMPLATE_FILE="$PLUGIN_DIR/gemini-hooks-settings.json"

    # Make hook scripts executable
    chmod +x "$HOOKS_DIR"/*.sh 2>/dev/null || true

    # Create .gemini directory if it doesn't exist
    mkdir -p "$GEMINI_SETTINGS_DIR"

    # Check if hooks need to be configured
    # Guard: if template is missing, skip auto-configure entirely
    if [ ! -f "$TEMPLATE_FILE" ]; then
        echo "⚠ Hooks template not found at: $TEMPLATE_FILE"
        echo "  Try: npm install -g @quantiya/codevibe@latest"
        return
    fi

    if [ ! -f "$GEMINI_SETTINGS_FILE" ]; then
        # No settings file — create from template
        echo "Configuring Gemini CLI hooks for CodeVibe..."
        sed "s|__CODEVIBE_HOOKS_DIR__|$HOOKS_DIR|g" "$TEMPLATE_FILE" > "$GEMINI_SETTINGS_FILE"
        echo "✓ Hooks configured at: $GEMINI_SETTINGS_FILE"
    elif ! grep -q "codevibe" "$GEMINI_SETTINGS_FILE" 2>/dev/null; then
        # Settings exists but no CodeVibe hooks present — append ours
        echo "Adding CodeVibe hooks to Gemini CLI settings..."
        if command -v jq >/dev/null 2>&1; then
            HOOKS_JSON=$(sed "s|__CODEVIBE_HOOKS_DIR__|$HOOKS_DIR|g" "$TEMPLATE_FILE")
            # Append CodeVibe hook entries to each hook array, preserving existing user hooks
            jq -s '
              .[0] as $existing | .[1] as $new |
              $existing | .hooks = (
                ($existing.hooks // {}) as $eh |
                ($new.hooks // {}) as $nh |
                ($eh | keys) + ($nh | keys) | unique | map(
                  { (.) : (($eh[.] // []) + ($nh[.] // [])) }
                ) | add // {}
              )
            ' "$GEMINI_SETTINGS_FILE" <(echo "$HOOKS_JSON") > "${GEMINI_SETTINGS_FILE}.tmp" && \
                mv "${GEMINI_SETTINGS_FILE}.tmp" "$GEMINI_SETTINGS_FILE" && \
                echo "✓ CodeVibe hooks added to: $GEMINI_SETTINGS_FILE" || \
                echo "⚠ Failed to add hooks. Delete $GEMINI_SETTINGS_FILE and restart to auto-configure."
        else
            echo "⚠ jq not found — cannot add hooks to existing settings."
            echo "  Delete $GEMINI_SETTINGS_FILE and restart to auto-configure."
        fi
    fi
}

# Configure hooks before starting
configure_hooks_if_needed

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

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

# Cleanup function to kill MCP server when wrapper exits
cleanup() {
    local wrapper_exit_code=$?
    log "Cleanup triggered"

    # Fire wrapper_exited telemetry BEFORE killing the server so the MCP
    # log is intact when we grep for SessionStart. 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 gemini_exit="unknown"
        if [ -f "$_CV_GEMINI_EXIT_FILE" ]; then
            gemini_exit="$(cat "$_CV_GEMINI_EXIT_FILE" 2>/dev/null | head -c 10 | tr -d '\n\r ')"
            [ -z "$gemini_exit" ] && gemini_exit="unknown"
        fi
        local lifetime=$(( $(date +%s) - _CV_STARTED_AT ))
        local gemini_lifetime=0
        if [ "$_CV_AGENT_STARTED_AT" -gt 0 ] 2>/dev/null; then
            gemini_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 gemini" — distinct from
        # "we invoked gemini 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 [ "$gemini_exit" != "unknown" ] && [ "$gemini_exit" != "0" ]; then
            outcome="error_exit"
        elif [ "$gemini_lifetime" -lt 5 ] 2>/dev/null; then
            outcome="early_exit"
        elif [ "$gemini_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,\"gemini_exit_code\":\"$gemini_exit\",\"gemini_lifetime_seconds\":$gemini_lifetime,\"tmux_session_started\":$_CV_TMUX_STARTED,\"agent_invoked\":$_CV_AGENT_INVOKED,\"session_start_hook_fired\":$hook_fired,\"terminal_outcome\":\"$outcome\""
    fi

    if [ -n "$MCP_PID" ] && kill -0 "$MCP_PID" 2>/dev/null; then
        log "Stopping MCP server (PID: $MCP_PID)"
        kill "$MCP_PID" 2>/dev/null || true
        wait "$MCP_PID" 2>/dev/null || true
    fi
    # Remove PID file
    rm -f "${CODEVIBE_TMPDIR}/codevibe-gemini-mcp-$$.pid"
    rm -f "$_CV_GEMINI_EXIT_FILE"
}

# Set up trap for cleanup
trap cleanup EXIT INT TERM

# 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 gemini is installed
if ! command -v gemini &> /dev/null; then
    echo "Error: gemini CLI is not installed."
    echo "Install from: https://github.com/google-gemini/gemini-cli"
    cv_failed "gemini_missing"
    sleep 1
    exit 1
fi

# Check if node is installed
if ! command -v node &> /dev/null; then
    echo "Error: Node.js is required but not installed."
    cv_failed "node_missing"
    sleep 1
    exit 1
fi

# Check if MCP server is built
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

# Generate a unique session name
SESSION_NAME="${TMUX_SESSION_PREFIX}-$$"
WORKING_DIR="$(pwd)"

log "Starting codevibe-gemini with session: $SESSION_NAME"
log "Working directory: $WORKING_DIR"
log "Arguments: $*"

# Check if we're already inside tmux.
# We deliberately do NOT `exec` here — running gemini 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
# (gemini remains the foreground process for the duration).
if [ -n "$TMUX" ]; then
    log "Already inside tmux, running gemini directly"
    _CV_AGENT_INVOKED="true"
    _CV_AGENT_STARTED_AT="$(date +%s)"
    # `|| _CV_RC=$?` is load-bearing: with `set -e`, a non-zero exit
    # from gemini would abort the wrapper before we capture the exit
    # code, leaving wrapper_exited with gemini_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
    gemini "$@" || _CV_RC=$?
    printf '%s' "$_CV_RC" > "$_CV_GEMINI_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 gemini directly"
    _CV_AGENT_INVOKED="true"
    _CV_AGENT_STARTED_AT="$(date +%s)"
    _CV_RC=0
    gemini "$@" || _CV_RC=$?
    printf '%s' "$_CV_RC" > "$_CV_GEMINI_EXIT_FILE" 2>/dev/null || true
    exit "$_CV_RC"
fi

# Start MCP server in background BEFORE launching Gemini
# The MCP server will watch the transcript file for changes
log "Starting MCP server..."
export GEMINI_WORKING_DIRECTORY="$WORKING_DIR"
export CODEVIBE_GEMINI_TMUX_SESSION="$SESSION_NAME"

# Start MCP server and capture its PID
node "$PLUGIN_DIR/dist/server.js" >> "$MCP_LOG_FILE" 2>&1 &
MCP_PID=$!
echo "$MCP_PID" > "${CODEVIBE_TMPDIR}/codevibe-gemini-mcp-$$.pid"

log "MCP server started with PID: $MCP_PID"

# Wait a moment for MCP server to initialize
sleep 1

# Check if MCP server is still running (exits if auth failed)
if ! kill -0 "$MCP_PID" 2>/dev/null; then
    log "ERROR: MCP server failed to start"
    # Show the last few lines of the log for context (e.g., auth error)
    echo ""
    tail -3 "$MCP_LOG_FILE" 2>/dev/null | grep -v '^\[' | head -1
    echo ""
    echo "Server failed to start. Check $MCP_LOG_FILE for details."
    cv_failed "server_died_on_startup"
    sleep 1
    exit 1
fi

# Create tmux session and run gemini
log "Creating tmux session: $SESSION_NAME"

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

# Create the session running gemini, then attach
# We use a wrapper that:
# 1. Exports the session name so prompts can find it
# 2. Runs gemini
# 3. Captures gemini's exit code to $_CV_GEMINI_EXIT_FILE for the
#    wrapper's cleanup trap (tmux's own attach exit code is independent
#    of the inner process exit, so the file is the only reliable signal)
# 4. Exits the tmux session when gemini exits

tmux new-session -d -s "$SESSION_NAME" -x "$(tput cols)" -y "$(tput lines)" \
    "export CODEVIBE_GEMINI_TMUX_SESSION='$SESSION_NAME'; export ENVIRONMENT='$ENVIRONMENT'; $GEMINI_CMD; printf '%s' \"\$?\" > '$_CV_GEMINI_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 mobile prompts
echo "$SESSION_NAME" > "${CODEVIBE_TMPDIR}/codevibe-gemini-tmux-session-$$"

log "Attaching to tmux session: $SESSION_NAME"

# Attach to the session
# This will show gemini's UI to the user
# When tmux session exits, the trap will clean up the MCP server
tmux attach-session -t "$SESSION_NAME"

# After tmux exits, cleanup is handled by trap
log "Tmux session ended"
