#!/bin/bash
#
# codevibe - CodeVibe CLI
#
# Manages authentication, updates, and plugin lifecycle.
# Authentication is shared across all CodeVibe plugins (Claude, Codex, Antigravity).
#
# Usage:
#   codevibe login              # Sign in via browser
#   codevibe logout             # Sign out
#   codevibe status             # Show auth status
#   codevibe update             # Update all CodeVibe packages + Claude plugin
#   codevibe version            # Show installed versions
#   codevibe reset-device       # Reset device identity (destructive)
#   codevibe --help             # Show this help
#
# Environment:
#   ENVIRONMENT                 # Set to 'production' (default) or 'development'
#

export ENVIRONMENT="${ENVIRONMENT:-production}"

# Colors
PURPLE='\033[0;35m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'

# Resolve symlinks to find the actual script location
SOURCE="${BASH_SOURCE[0]}"
while [ -L "$SOURCE" ]; do
  DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
  SOURCE="$(readlink "$SOURCE")"
  [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
done
SCRIPT_DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
PACKAGE_DIR="$(dirname "$SCRIPT_DIR")"

# ─── Help ────────────────────────────────────────────────────────────

show_help() {
  echo ""
  echo -e "${BOLD}CodeVibe${NC} — Control AI coding agents from your phone"
  echo ""
  echo "Usage: codevibe <command>"
  echo ""
  echo "Commands:"
  echo "  login          Sign in with Apple or Google (opens browser)"
  echo "  logout         Sign out"
  echo "  status         Show authentication status"
  echo "  update         Update all CodeVibe packages and Claude plugin"
  echo "  version        Show installed package versions"
  echo "  reset-device   Reset device identity (destructive)"
  echo "  orchestration  Configure Quorum 2.0 reviewer policy (enable/disable/configure)"
  echo ""
  echo "Run 'codevibe' with no arguments to launch the orchestration shell"
  echo "(Pro/Max only — Free tier hands off to Companion mode)."
  echo "Use --mode {companion|orchestration} to override mode selection."
  echo ""
  echo "Agent wrappers:"
  echo "  codevibe-claude    Start Claude Code with mobile sync"
  echo "  codevibe-codex     Start Codex CLI with mobile sync"
  echo "  codevibe-agy       Start Antigravity CLI with mobile sync"
  echo ""
  echo -e "${DIM}https://quantiya.ai/codevibe${NC}"
  echo ""
}

# ─── Version ─────────────────────────────────────────────────────────

show_version() {
  echo ""
  echo -e "${BOLD}CodeVibe Versions${NC}"
  echo ""

  # Meta-package version
  if [ -f "$PACKAGE_DIR/package.json" ]; then
    META_VER=$(node -p "require('$PACKAGE_DIR/package.json').version" 2>/dev/null || echo "?")
    echo -e "  @quantiya/codevibe              ${GREEN}$META_VER${NC}"
  fi

  # Core version
  CORE_PKG="$PACKAGE_DIR/node_modules/@quantiya/codevibe-core/package.json"
  if [ -f "$CORE_PKG" ]; then
    CORE_VER=$(node -p "require('$CORE_PKG').version" 2>/dev/null || echo "?")
    echo -e "  @quantiya/codevibe-core          ${GREEN}$CORE_VER${NC}"
  fi

  # Plugin versions
  for plugin in claude codex antigravity; do
    PKG="$PACKAGE_DIR/node_modules/@quantiya/codevibe-${plugin}-plugin/package.json"
    if [ -f "$PKG" ]; then
      VER=$(node -p "require('$PKG').version" 2>/dev/null || echo "?")
      echo -e "  @quantiya/codevibe-${plugin}-plugin  ${GREEN}$VER${NC}"
    fi
  done

  # Agent CLI versions (only printed for CLIs actually installed)
  local _printed_cli_header=false
  _print_cli_section_header() {
    if [ "$_printed_cli_header" = false ]; then
      echo ""
      _printed_cli_header=true
    fi
  }

  if command -v claude >/dev/null 2>&1; then
    CLAUDE_VER=$(claude --version 2>/dev/null | head -1 || echo "?")
    _print_cli_section_header
    echo -e "  Claude Code                      ${GREEN}$CLAUDE_VER${NC}"
  fi
  if command -v codex >/dev/null 2>&1; then
    CODEX_VER=$(codex --version 2>/dev/null | head -1 || echo "?")
    _print_cli_section_header
    echo -e "  Codex CLI                        ${GREEN}$CODEX_VER${NC}"
  fi
  if command -v agy >/dev/null 2>&1; then
    AGY_VER=$(agy --version 2>/dev/null | head -1 || echo "?")
    _print_cli_section_header
    echo -e "  Antigravity CLI                  ${GREEN}$AGY_VER${NC}"
  fi

  echo ""
}

# ─── Update ──────────────────────────────────────────────────────────
#
# NOTE: Claude Code's plugin lifecycle has known bugs as of Claude Code 2.1.x:
#   1. `claude plugin update` lies — reports "already at latest version" based on
#      the installed plugin's own package.json, never consults the marketplace
#      manifest for the authoritative latest version.
#   2. `claude plugin install` doesn't refetch — if a cache directory for the
#      plugin exists, it reuses it silently instead of downloading the version
#      pinned in the marketplace manifest.
#   3. `claude plugin uninstall` doesn't delete the cached plugin directory —
#      only removes the installed_plugins.json entry, leaving stale code on disk
#      for the next install to silently reuse.
#
# This function works around all three by detecting when the installed version
# on disk doesn't match the version the marketplace says it should be, then
# manually clearing all stale cache paths before reinstalling and verifying.
#
# If Anthropic fixes these bugs, the cache-nuking path is a no-op (the version
# comparison will always match) and the code stays correct.

_read_marketplace_version() {
  # Read the pinned source.version for codevibe-claude from the marketplace manifest.
  # Echoes the version string, or empty on failure.
  local manifest="$HOME/.claude/plugins/marketplaces/codevibe-marketplace/.claude-plugin/marketplace.json"
  [ -f "$manifest" ] || return 0
  node -p "
    try {
      const m = require('$manifest');
      const p = (m.plugins || []).find(x => x.name === 'codevibe-claude');
      (p && p.source && p.source.version) || '';
    } catch (e) { '' }
  " 2>/dev/null
}

_read_installed_version() {
  # Read the installed version of codevibe-claude from installed_plugins.json.
  # Echoes the version string, or empty if not installed.
  local db="$HOME/.claude/plugins/installed_plugins.json"
  [ -f "$db" ] || return 0
  node -p "
    try {
      const ip = require('$db');
      const e = (ip.plugins || {})['codevibe-claude@codevibe-marketplace'];
      (e && e[0] && e[0].version) || '';
    } catch (e) { '' }
  " 2>/dev/null
}

_nuke_claude_plugin_cache() {
  # Remove every path Claude Code's plugin loader might read stale code from.
  rm -rf "$HOME/.claude/plugins/cache/codevibe-marketplace/codevibe-claude"
  rm -rf "$HOME/.claude/plugins/npm-cache/node_modules/@quantiya/codevibe-claude-plugin"
  rm -rf "$HOME/.claude/plugins/npm-cache/node_modules/codevibe-claude-plugin"
  rm -f  "$HOME/.claude/plugins/npm-cache/package-lock.json"
}

do_update() {
  echo ""
  echo -e "${BOLD}${PURPLE}CodeVibe Update${NC}"
  echo ""

  # Show current meta-package version
  if [ -f "$PACKAGE_DIR/package.json" ]; then
    CUR_VER=$(node -p "require('$PACKAGE_DIR/package.json').version" 2>/dev/null || echo "?")
    echo -e "  ${DIM}Current meta-package: $CUR_VER${NC}"
  fi
  echo ""

  # ─── Step 1: Update the global meta-package (pulls latest core + plugins) ──
  echo -e "${PURPLE}▸${NC} Updating @quantiya/codevibe..."
  # Capture output so we can check npm's exit status directly instead of the
  # pipe's (tail's exit status would mask npm failures without `set -o pipefail`).
  local npm_output
  if ! npm_output=$(npm install -g --force @quantiya/codevibe@latest 2>&1); then
    echo "$npm_output" | tail -10
    echo ""
    echo -e "${YELLOW}!${NC} Meta-package update failed. Check the output above, fix the issue, and retry 'codevibe update'."
    return 1
  fi
  echo "$npm_output" | tail -3
  echo -e "${GREEN}✓${NC} Meta-package updated"

  # ─── Step 2: Update the Claude Code plugin ─────────────────────────────────
  if ! command -v claude >/dev/null 2>&1; then
    echo ""
    echo -e "${YELLOW}!${NC} claude CLI not found — skipping Claude Code plugin update"
    echo ""
    echo -e "${BOLD}Updated versions:${NC}"
    # Same in-place-update gotcha as the success path — `exec "$0" version`
    # re-runs the just-installed script bytes instead of the OLD in-memory
    # show_version that was loaded before npm overwrote us. This branch
    # also returns 0, so swapping `show_version + return 0` for `exec`
    # preserves exit semantics.
    exec "$0" version
  fi

  echo ""
  echo -e "${PURPLE}▸${NC} Updating Claude Code plugin..."

  # Refresh the marketplace manifest so we see the latest pinned version.
  # Errors here are non-fatal at this step — the previously-cached manifest may
  # still be usable — but the EXPECTED_VERSION read below is the real gate.
  if claude plugin marketplace update codevibe-marketplace; then
    echo -e "${GREEN}✓${NC} Marketplace refreshed"
  else
    echo -e "${YELLOW}!${NC} Marketplace refresh failed (falling back to cached manifest)"
  fi

  local EXPECTED_VERSION
  local INSTALLED_VERSION
  EXPECTED_VERSION=$(_read_marketplace_version)
  INSTALLED_VERSION=$(_read_installed_version)

  # EXPECTED_VERSION is required — without it we have no ground truth to verify
  # against, so the whole cache-bug workaround is blind. Fail loudly rather than
  # claim success we can't prove.
  if [ -z "$EXPECTED_VERSION" ]; then
    echo ""
    echo -e "${YELLOW}!${NC} Could not read expected version from marketplace manifest."
    echo -e "  ${DIM}Expected at: \$HOME/.claude/plugins/marketplaces/codevibe-marketplace/.claude-plugin/marketplace.json${NC}"
    echo -e "  ${DIM}This usually means the marketplace refresh failed or the manifest schema has drifted.${NC}"
    echo -e "  ${DIM}Try: claude plugin marketplace update codevibe-marketplace${NC}"
    return 1
  fi

  echo -e "  ${DIM}installed: ${INSTALLED_VERSION:-<none>}   marketplace: ${EXPECTED_VERSION}${NC}"

  # ─── Step 3: Decide if we need to force a cache-clearing reinstall ─────────
  local NEEDS_REINSTALL=false
  if [ -z "$INSTALLED_VERSION" ] || [ "$INSTALLED_VERSION" != "$EXPECTED_VERSION" ]; then
    NEEDS_REINSTALL=true
    if [ -n "$INSTALLED_VERSION" ]; then
      echo -e "${YELLOW}!${NC} Cache is stale (${INSTALLED_VERSION} on disk, ${EXPECTED_VERSION} in marketplace)."
      echo -e "  ${DIM}Working around Claude Code plugin update bug...${NC}"
    fi
  fi

  if [ "$NEEDS_REINSTALL" = "true" ]; then
    # Must uninstall + nuke cache BEFORE install. `claude plugin install` will
    # silently reuse an existing cache dir even if the marketplace version changed.
    claude plugin uninstall codevibe-claude@codevibe-marketplace >/dev/null 2>&1 || true
    _nuke_claude_plugin_cache

    if claude plugin install codevibe-claude@codevibe-marketplace; then
      echo -e "${GREEN}✓${NC} Claude plugin reinstalled"
    else
      echo ""
      echo -e "${YELLOW}!${NC} Claude plugin install failed — try running 'codevibe update' again"
      return 1
    fi
  else
    # Version already matches. Still call install to make sure installed_plugins.json
    # is in sync, but treat any error as non-fatal since we're not actually changing state.
    claude plugin install codevibe-claude@codevibe-marketplace >/dev/null 2>&1 || true
    echo -e "${GREEN}✓${NC} Claude plugin already at ${INSTALLED_VERSION}"
  fi

  # ─── Step 4: Verify the installed version is now what we expected ──────────
  # EXPECTED_VERSION is guaranteed non-empty here (we bailed earlier otherwise),
  # so the verification is unconditional.
  INSTALLED_VERSION=$(_read_installed_version)
  if [ "$INSTALLED_VERSION" != "$EXPECTED_VERSION" ]; then
    echo ""
    echo -e "${YELLOW}!${NC} WARNING: Installed version (${INSTALLED_VERSION:-<none>}) still doesn't match expected (${EXPECTED_VERSION})."
    echo -e "  ${DIM}Try: rm -rf ~/.claude/plugins/cache/codevibe-marketplace/codevibe-claude && codevibe update${NC}"
    return 1
  fi

  # ─── Step 5: Tell the user about running sessions ──────────────────────────
  if [ "$NEEDS_REINSTALL" = "true" ]; then
    echo ""
    echo -e "${YELLOW}!${NC} Running Claude Code sessions still have the OLD plugin server in memory."
    echo -e "  ${DIM}Exit and restart any active session to pick up the new plugin.${NC}"
  fi

  echo ""

  # Show new versions.
  # `exec "$0" version` re-runs the (now-updated-on-disk) script in a fresh
  # bash process, so the post-update output reflects the JUST-INSTALLED
  # version's `show_version`, not the OLD function definitions that this
  # bash process loaded into memory BEFORE `npm install -g` overwrote the
  # script on disk. Without this, an upgrade like 1.0.50 → 1.0.51 displays
  # the old script's versions block (e.g. missing a freshly-added plugin)
  # even though the new bytes are already installed.
  echo -e "${BOLD}Updated versions:${NC}"
  exec "$0" version
}

# ─── Command routing ─────────────────────────────────────────────────

# CodeVibe 2.0 CP-1.a: when no recognized subcommand is provided AND
# argv either contains a mode flag (`--mode|--orchestration|--companion`)
# OR no args at all, dispatch to the orchestration-shell CLI bundled
# inside @quantiya/codevibe-core. Existing subcommands keep their Bash
# routing.
#
# The dispatch arm sits BELOW the existing case statement; the case
# statement falls through to the default `*` arm only when the first
# token is not recognized AND not "" (handled by --help arm). The
# 2.0-mode dispatch is checked BEFORE the unknown-command exit.

case "$1" in
  --help|-h|help)
    show_help
    exit 0
    ;;
  version|--version|-v)
    show_version
    exit 0
    ;;
  update)
    do_update
    exit $?
    ;;
  login|logout|status|reset-device|orchestration)
    # Delegate to codevibe-core CLI. `orchestration` is a Quorum 2.0
    # subcommand (2f.0.a.4) with its own sub-dispatch for
    # enable/disable/status/configure — routed through runAuthCli's
    # switch so core's CLI entry point stays the single Node entry.
    CORE_CLI="$PACKAGE_DIR/node_modules/@quantiya/codevibe-core/bin/codevibe.js"
    if [ ! -f "$CORE_CLI" ]; then
      # Local-dev fallback: meta + core repos as workspace siblings, so
      # `./bin/codevibe login` works when invoked directly from a checked-out
      # codevibe repo without `npm install`. No effect on global install
      # since the primary path resolves first.
      WORKSPACE_CLI="$(dirname "$PACKAGE_DIR")/codevibe-core/bin/codevibe.js"
      if [ -f "$WORKSPACE_CLI" ]; then
        CORE_CLI="$WORKSPACE_CLI"
      else
        echo "Error: @quantiya/codevibe-core not found. Try reinstalling: npm install -g @quantiya/codevibe" >&2
        exit 1
      fi
    fi
    exec node "$CORE_CLI" "$@"
    ;;
  model|""|--mode|--orchestration|--companion|--agent|--forget-agent|--)
    # CodeVibe 2.0 CP-1.a: empty args OR any mode/agent/forget flag OR
    # the POSIX `--` argv terminator → orchestration shell. The TS
    # parser handles flag re-ordering + the `--` separator. Per
    # Stage 2 r2 Codex MEDIUM-1.
    #
    # `model` (install/status/remove/health-check) also routes here: the
    # orchestration-shell CLI owns the local Gemma runtime, so it dispatches
    # the `model` subcommand rather than launching the shell. Without this,
    # `codevibe model ...` fell through to the unknown-command arm on a real
    # host install (it only worked in-container where cli.js is invoked
    # directly).
    SHELL_CLI=$(node -e "try { console.log(require.resolve('@quantiya/codevibe-core/dist/orchestration-shell/cli.js')) } catch(e) { process.exit(1) }" 2>/dev/null)
    if [ -n "$SHELL_CLI" ] && [ -f "$SHELL_CLI" ]; then
      exec node "$SHELL_CLI" "$@"
    fi
    # Fallback to a local lookup relative to this script.
    SHELL_CLI_LOCAL="$PACKAGE_DIR/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js"
    if [ -f "$SHELL_CLI_LOCAL" ]; then
      exec node "$SHELL_CLI_LOCAL" "$@"
    fi
    # Workspace-sibling fallback: meta + core repos as siblings, so
    # `./bin/codevibe --mode orchestration` works directly from a
    # checked-out workspace without `npm install`. No effect on global
    # install (primary path + node_modules fallback resolve first).
    SHELL_CLI_WORKSPACE="$(dirname "$PACKAGE_DIR")/codevibe-core/dist/orchestration-shell/cli.js"
    if [ -f "$SHELL_CLI_WORKSPACE" ]; then
      exec node "$SHELL_CLI_WORKSPACE" "$@"
    fi
    echo "Error: codevibe-core orchestration-shell CLI not found. Try reinstalling: npm install -g @quantiya/codevibe" >&2
    exit 1
    ;;
  *)
    echo "Unknown command: $1" >&2
    echo "Run 'codevibe --help' for usage." >&2
    exit 1
    ;;
esac
