#!/usr/bin/env sh
set -eu

# --- Inner installer telemetry (refs piglet12138/claude-code#184) ---
# Loaded BEFORE any variable validation, need_cmd, or anything that can
# fail. So early aborts (missing node/npm/curl, bad THKJ_CHANNEL,
# unwritable dirs, etc.) still produce a terminal funnel event.
# Helpers themselves depend only on POSIX sh + uname + curl, never on
# node or npm.

LUCKY_INNER_TELEMETRY_URL="${LUCKY_INNER_TELEMETRY_URL:-https://claude-zh.cn/api/install-event}"
INNER_FAIL_REASON=""
TMP_DIR=""

lucky_outer_detect_platform() {
  case "$(uname -s 2>/dev/null)" in
    Darwin) printf macos ;;
    Linux)
      if grep -qi microsoft /proc/version 2>/dev/null; then
        printf wsl
      else
        printf linux
      fi
      ;;
    *) printf unknown ;;
  esac
}

lucky_outer_detect_arch() {
  case "$(uname -m 2>/dev/null)" in
    x86_64|amd64) printf x64 ;;
    aarch64|arm64) printf arm64 ;;
    armv7l|armv6l|arm) printf arm ;;
    i386|i686) printf x86 ;;
    *) printf unknown ;;
  esac
}

lucky_outer_gen_id() {
  if [ -r /proc/sys/kernel/random/uuid ]; then
    _id="$(cat /proc/sys/kernel/random/uuid 2>/dev/null | tr -d '\r\n' || true)"
    [ -n "$_id" ] && { printf '%s' "$_id"; return 0; }
  fi
  if command -v uuidgen >/dev/null 2>&1; then
    _id="$(uuidgen 2>/dev/null | tr -d '\r\n' || true)"
    [ -n "$_id" ] && { printf '%s' "$_id"; return 0; }
  fi
  head -c 16 /dev/urandom 2>/dev/null | od -An -tx1 2>/dev/null | tr -d ' \n' | head -c 32
}

LUCKY_PLATFORM="$(lucky_outer_detect_platform)"
LUCKY_ARCH="$(lucky_outer_detect_arch)"
LUCKY_INSTALL_ID="${LUCKY_INSTALL_ID:-$(lucky_outer_gen_id)}"

lucky_inner_install_event() {
  stage="$1"
  [ "${LUCKY_DISABLE_TELEMETRY:-0}" = "1" ] && return 0
  [ -z "$LUCKY_INNER_TELEMETRY_URL" ] && return 0
  _url="${LUCKY_INNER_TELEMETRY_URL%/}/install.sh/${LUCKY_PLATFORM}/${LUCKY_ARCH}/${stage}"
  _shell_raw="${SHELL:-unknown}"
  _shell_name="${_shell_raw##*/}"
  case "$_shell_name" in
    [a-zA-Z0-9]*) ;;
    *) _shell_name="unknown" ;;
  esac
  _full="${_url}?shell=${_shell_name}&install_id=${LUCKY_INSTALL_ID}"
  # Background the request so install.sh main flow never blocks on
  # telemetry latency (real users on CN→CF can see 2s per beacon).
  # Orphaned curl/wget keeps running after install.sh exits, bounded
  # by --max-time. Same curl-or-wget fallback as the main flow.
  if command -v curl >/dev/null 2>&1; then
    ( curl -fsS --max-time 2 "$_full" >/dev/null 2>&1 ) &
  elif command -v wget >/dev/null 2>&1; then
    ( wget -qO- --timeout=2 --tries=1 "$_full" >/dev/null 2>&1 ) &
  fi
}

# Trap fires for any exit (normal, set -e abort, signal). If a failure
# path set INNER_FAIL_REASON, that specific event is sent; otherwise the
# generic catchall. Run BEFORE need_cmd/variable validation so early
# aborts are visible.
cleanup() {
  _exit_code=$?
  if [ "$_exit_code" -eq 0 ]; then
    lucky_inner_install_event inner_install_success
  else
    if [ -n "$INNER_FAIL_REASON" ]; then
      lucky_inner_install_event "$INNER_FAIL_REASON"
    else
      lucky_inner_install_event inner_install_failed
    fi
  fi
  [ -n "$TMP_DIR" ] && rm -rf "$TMP_DIR"
}
trap cleanup EXIT INT TERM

lucky_inner_install_event inner_start

# --- end inner installer telemetry ---

PACKAGE_URL="${THKJ_PACKAGE_URL:-}"
BRIDGE_API_BASE_URL_EXPLICIT=0
if [ -n "${THKJ_BRIDGE_API_BASE_URL:-}" ]; then
  BRIDGE_API_BASE_URL_EXPLICIT=1
fi
BRIDGE_API_BASE_URL="${THKJ_BRIDGE_API_BASE_URL:-https://bridge.annealing.cn}"
BIN_DIR="${THKJ_BIN_DIR:-$HOME/.local/bin}"
CHANNEL="${THKJ_CHANNEL:-stable}"
case "$CHANNEL" in
  ''|*[!A-Za-z0-9._-]*)
    INNER_FAIL_REASON=inner_bad_channel
    echo "Invalid THKJ_CHANNEL: $CHANNEL" >&2
    echo "Use letters, numbers, dot, underscore, or dash only." >&2
    exit 1
    ;;
esac
if [ "$CHANNEL" = "stable" ]; then
  DEFAULT_BIN_NAME="lucky"
else
  DEFAULT_BIN_NAME="lucky-$CHANNEL"
fi
BIN_NAME="${THKJ_BIN_NAME:-$DEFAULT_BIN_NAME}"
case "$BIN_NAME" in
  ''|*[!A-Za-z0-9._-]*)
    INNER_FAIL_REASON=inner_bad_bin_name
    echo "Invalid THKJ_BIN_NAME: $BIN_NAME" >&2
    echo "Use letters, numbers, dot, underscore, or dash only." >&2
    exit 1
    ;;
esac
if [ -n "${THKJ_INSTALL_DIR:-}" ]; then
  INSTALL_DIR="$THKJ_INSTALL_DIR"
else
  INSTALL_DIR="$HOME/.local/share/thkj-claude-code/$CHANNEL"
fi
VERSION="${THKJ_VERSION:-2.1.97}"
VERSION_URL="${THKJ_VERSION_URL:-}"

if [ -z "$BRIDGE_API_BASE_URL" ]; then
  if [ -n "${THKJ_INSTALL_BASE_URL:-}" ]; then
    BRIDGE_API_BASE_URL="${THKJ_INSTALL_BASE_URL%/}"
  else
    BRIDGE_API_BASE_URL="http://127.0.0.1:8787"
  fi
fi

if [ "$BRIDGE_API_BASE_URL_EXPLICIT" = "0" ] && [ "$CHANNEL" != "stable" ] && [ "${THKJ_USE_PREVIEW_BRIDGE:-1}" != "0" ]; then
  case "$BRIDGE_API_BASE_URL" in
    */previews/$CHANNEL) ;;
    *) BRIDGE_API_BASE_URL="${BRIDGE_API_BASE_URL%/}/previews/$CHANNEL" ;;
  esac
fi

if [ -z "$VERSION_URL" ]; then
  if [ "$CHANNEL" = "stable" ]; then
    VERSION_URL="${BRIDGE_API_BASE_URL%/}/version"
  else
    VERSION_URL="${BRIDGE_API_BASE_URL%/}/channels/$CHANNEL/version.json"
  fi
fi

if [ -z "$PACKAGE_URL" ]; then
  if [ "$CHANNEL" = "stable" ]; then
    PACKAGE_URL="${BRIDGE_API_BASE_URL%/}/download/thkj-claude-code.tgz"
  else
    PACKAGE_URL="${BRIDGE_API_BASE_URL%/}/channels/$CHANNEL/thkj-claude-code.tgz"
  fi
fi

need_cmd() {
  if ! command -v "$1" >/dev/null 2>&1; then
    case "$1" in
      node|npm|curl|wget|sh|tar|sha256sum)
        INNER_FAIL_REASON="inner_missing_$1"
        ;;
      *)
        INNER_FAIL_REASON=inner_missing_cmd
        ;;
    esac
    echo "Missing required command: $1" >&2
    exit 1
  fi
}

prepend_path_dir() {
  _thkj_path_dir="$1"
  [ -n "$_thkj_path_dir" ] || return 0
  [ -d "$_thkj_path_dir" ] || return 0
  case ":$PATH:" in
    *":$_thkj_path_dir:"*) ;;
    *)
      PATH="$_thkj_path_dir:$PATH"
      export PATH
      ;;
  esac
}

path_contains_dir() {
  _thkj_path_dir="$1"
  [ -n "$_thkj_path_dir" ] || return 1
  case ":$PATH:" in
    *":$_thkj_path_dir:"*) return 0 ;;
    *) return 1 ;;
  esac
}

refresh_macos_homebrew_path() {
  [ "$LUCKY_PLATFORM" = "macos" ] || return 0
  if [ -n "${THKJ_HOMEBREW_PREFIX:-}" ]; then
    prepend_path_dir "$THKJ_HOMEBREW_PREFIX/bin"
    prepend_path_dir "$THKJ_HOMEBREW_PREFIX/opt/node/bin"
  else
    prepend_path_dir /opt/homebrew/bin
    prepend_path_dir /usr/local/bin
  fi
  _thkj_brew="$(command -v brew 2>/dev/null || true)"
  if [ -n "$_thkj_brew" ]; then
    _thkj_brew_prefix="$("$_thkj_brew" --prefix 2>/dev/null || true)"
    if [ -n "$_thkj_brew_prefix" ]; then
      prepend_path_dir "$_thkj_brew_prefix/bin"
      prepend_path_dir "$_thkj_brew_prefix/opt/node/bin"
    fi
  fi
}

find_macos_homebrew() {
  refresh_macos_homebrew_path
  _thkj_brew="$(command -v brew 2>/dev/null || true)"
  if [ -n "$_thkj_brew" ]; then
    printf '%s' "$_thkj_brew"
    return 0
  fi
  if [ -n "${THKJ_HOMEBREW_PREFIX:-}" ] && [ -x "$THKJ_HOMEBREW_PREFIX/bin/brew" ]; then
    printf '%s' "$THKJ_HOMEBREW_PREFIX/bin/brew"
    return 0
  fi
  for _thkj_brew_candidate in /opt/homebrew/bin/brew /usr/local/bin/brew; do
    if [ -x "$_thkj_brew_candidate" ]; then
      printf '%s' "$_thkj_brew_candidate"
      return 0
    fi
  done
  return 1
}

# Minimum Node major we accept. Kept in sync with the outer installer
# (claude-zh.cn install.sh / install.ps1).
LUCKY_MIN_NODE_MAJOR="${LUCKY_MIN_NODE_MAJOR:-24}"
# Node LTS we extract on Linux/WSL when none is present.
LUCKY_NODE_TARBALL_VERSION="${LUCKY_NODE_TARBALL_VERSION:-v24.18.0}"
# Where the Linux/WSL tarball is extracted. Overridable so rootless users can
# point at a writable prefix (e.g. $HOME/.local) and skip sudo entirely.
LUCKY_NODE_PREFIX="${LUCKY_NODE_PREFIX:-/usr/local}"

node_major_version() {
  _thkj_node_version="$(node --version 2>/dev/null | sed 's/^v//' | sed 's/[.].*//' || true)"
  case "$_thkj_node_version" in
    ''|*[!0-9]*) printf 0 ;;
    *) printf '%s' "$_thkj_node_version" ;;
  esac
}

has_node_runtime() {
  command -v node >/dev/null 2>&1 || return 1
  command -v npm >/dev/null 2>&1 || return 1
  _thkj_node_major="$(node_major_version)"
  [ "$_thkj_node_major" -ge "$LUCKY_MIN_NODE_MAJOR" ] 2>/dev/null
}

# npmmirror (Aliyun CDN) prebuilt Node tarball matching the detected arch.
# Empty for arches we don't ship a tarball for. Mainland-first, no apt churn.
recommended_node_url() {
  case "$LUCKY_ARCH" in
    x64)   _thkj_node_arch="linux-x64" ;;
    arm64) _thkj_node_arch="linux-arm64" ;;
    arm)   _thkj_node_arch="linux-armv7l" ;;
    *)     return 1 ;;
  esac
  printf "https://npmmirror.com/mirrors/node/%s/node-%s-%s.tar.xz" \
    "$LUCKY_NODE_TARBALL_VERSION" "$LUCKY_NODE_TARBALL_VERSION" "$_thkj_node_arch"
}

# Make a freshly extracted $LUCKY_NODE_PREFIX/bin/node visible in the current
# shell without requiring a terminal restart.
refresh_linux_node_path() {
  case ":$PATH:" in
    *":$LUCKY_NODE_PREFIX/bin:"*) ;;
    *) PATH="$LUCKY_NODE_PREFIX/bin:$PATH"; export PATH ;;
  esac
  hash -r 2>/dev/null || true
}

# Silent Node bootstrap for Linux/WSL: extract the npmmirror tarball into
# $LUCKY_NODE_PREFIX (sudo only when that prefix isn't user-writable). Mirrors
# install-lucky.sh so the bare bridge URL is self-sufficient. Non-zero on any
# failure -> caller falls back to a manual hint.
install_linux_node_runtime() {
  _thkj_node_url="${LUCKY_NODE_TARBALL_URL:-$(recommended_node_url 2>/dev/null || true)}"
  [ -n "$_thkj_node_url" ] || return 1
  command -v curl >/dev/null 2>&1 || return 1
  command -v tar >/dev/null 2>&1 || return 1

  # Prefer a sudo-free install when the prefix is already writable; only reach
  # for sudo when it isn't (and bail if we'd need it but can't get it).
  _thkj_node_sudo=""
  if ! { [ -d "$LUCKY_NODE_PREFIX" ] && [ -w "$LUCKY_NODE_PREFIX" ]; }; then
    if [ "$(id -u 2>/dev/null || printf 1)" != "0" ]; then
      if command -v sudo >/dev/null 2>&1; then
        _thkj_node_sudo="sudo"
      else
        return 1
      fi
    fi
  fi

  echo "Node.js ${LUCKY_MIN_NODE_MAJOR}+ not found. Installing Node.js LTS via npmmirror..." >&2
  if [ -n "$_thkj_node_sudo" ]; then
    curl -fsSL "$_thkj_node_url" | sudo tar -xJ -C "$LUCKY_NODE_PREFIX" --strip-components=1 || return 1
  else
    curl -fsSL "$_thkj_node_url" | tar -xJ -C "$LUCKY_NODE_PREFIX" --strip-components=1 || return 1
  fi
  refresh_linux_node_path
  has_node_runtime
}

node_runtime_fail_reason() {
  if command -v node >/dev/null 2>&1 && ! command -v npm >/dev/null 2>&1; then
    printf inner_missing_npm
  else
    printf inner_missing_node
  fi
}

ensure_node_runtime() {
  refresh_macos_homebrew_path
  if has_node_runtime; then
    return 0
  fi

  if [ "${THKJ_SKIP_DEPS_INSTALL:-0}" = "1" ]; then
    INNER_FAIL_REASON="$(node_runtime_fail_reason)"
    echo "Missing required Node.js ${LUCKY_MIN_NODE_MAJOR}+ runtime or npm." >&2
    echo "Install Node.js ${LUCKY_MIN_NODE_MAJOR} or newer before installing lucky." >&2
    exit 1
  fi

  if [ "$LUCKY_PLATFORM" = "macos" ]; then
    _thkj_brew="$(find_macos_homebrew || true)"
    if [ -n "$_thkj_brew" ]; then
      echo "Node.js ${LUCKY_MIN_NODE_MAJOR}+ and npm were not found. Installing Node.js with Homebrew..." >&2
      if "$_thkj_brew" install node; then
        refresh_macos_homebrew_path
        if has_node_runtime; then
          return 0
        fi
        INNER_FAIL_REASON="$(node_runtime_fail_reason)"
        echo "Homebrew installed Node.js, but node/npm are still not visible in this shell." >&2
        echo "Make sure Homebrew is on PATH and re-run the lucky installer." >&2
        exit 1
      fi
      INNER_FAIL_REASON="$(node_runtime_fail_reason)"
      echo "Homebrew could not install Node.js. Install Node.js ${LUCKY_MIN_NODE_MAJOR}+ manually and re-run the lucky installer." >&2
      exit 1
    fi

    INNER_FAIL_REASON="$(node_runtime_fail_reason)"
    echo "Missing required Node.js ${LUCKY_MIN_NODE_MAJOR}+ runtime or npm." >&2
    echo "Homebrew was not found, so the installer cannot install Node.js automatically." >&2
    echo "Install Homebrew and run: brew install node" >&2
    echo "Then re-run: curl -fsSL https://bridge.annealing.cn/install.sh | sh" >&2
    exit 1
  fi

  # Linux / WSL: try a silent npmmirror tarball install before giving up, so
  # the bare bridge URL bootstraps Node on its own (parity with the outer
  # install-lucky.sh wrapper). THKJ_SKIP_DEPS_INSTALL already short-circuited.
  if [ "$LUCKY_PLATFORM" = "wsl" ] || [ "$LUCKY_PLATFORM" = "linux" ]; then
    if install_linux_node_runtime; then
      return 0
    fi
  fi

  INNER_FAIL_REASON="$(node_runtime_fail_reason)"
  echo "Missing required Node.js ${LUCKY_MIN_NODE_MAJOR}+ runtime or npm, and automatic install did not succeed." >&2
  echo "Install Node.js ${LUCKY_MIN_NODE_MAJOR} or newer, then re-run: curl -fsSL https://bridge.annealing.cn/install.sh | sh" >&2
  exit 1
}

ensure_node_runtime

SH_BIN="$(command -v sh || true)"
if [ -z "$SH_BIN" ]; then
  SH_BIN="/bin/sh"
fi
NODE_BIN="$(command -v node || true)"
NODE_BIN_DIR=""
if [ -n "$NODE_BIN" ]; then
  NODE_BIN_DIR="$(dirname "$NODE_BIN")"
fi
NODE_USE_SYSTEM_CA=0
if [ "$LUCKY_PLATFORM" = "macos" ] && [ -n "$NODE_BIN" ] && "$NODE_BIN" --help 2>/dev/null | grep -q -- '--use-system-ca'; then
  NODE_USE_SYSTEM_CA=1
fi

export NPM_CONFIG_UPDATE_NOTIFIER=false
export NPM_CONFIG_FUND=false
export NPM_CONFIG_AUDIT=false

TMP_BASE="${THKJ_TMP_DIR:-${CLAUDE_CODE_TMPDIR:-$HOME/tmp}}"
mkdir -p "$BIN_DIR" "$INSTALL_DIR" "$TMP_BASE"
TMP_DIR="$(mktemp -d "$TMP_BASE/thkj-install.XXXXXX")"

TARBALL="$TMP_DIR/thkj-claude-code.tgz"
VERSION_JSON="$TMP_DIR/version.json"
if command -v curl >/dev/null 2>&1; then
  if ! curl -fsSL "$PACKAGE_URL" -o "$TARBALL"; then
    INNER_FAIL_REASON=inner_download_failed
    exit 1
  fi
  curl -fsSL "$VERSION_URL" -o "$VERSION_JSON" || true
elif command -v wget >/dev/null 2>&1; then
  if ! wget -qO "$TARBALL" "$PACKAGE_URL"; then
    INNER_FAIL_REASON=inner_download_failed
    exit 1
  fi
  wget -qO "$VERSION_JSON" "$VERSION_URL" || true
else
  INNER_FAIL_REASON=inner_missing_downloader
  echo "Missing required command: curl or wget" >&2
  exit 1
fi
lucky_inner_install_event inner_download_success
if [ ! -s "$VERSION_JSON" ]; then
  # Version metadata fetch failed (tarball still OK). Worth a beacon so
  # we can quantify "stale metadata host" vs real install failures.
  lucky_inner_install_event inner_metadata_unavailable
fi

EXPECTED_SHA256=""
METADATA_GIT_SHA=""
if [ -s "$VERSION_JSON" ]; then
  METADATA_VERSION="$(node -e "try{const fs=require('fs'); const data=JSON.parse(fs.readFileSync(process.argv[1],'utf8')); process.stdout.write(data.version||'')}catch{}" "$VERSION_JSON")"
  if [ -n "$METADATA_VERSION" ]; then
    VERSION="$METADATA_VERSION"
  fi
  EXPECTED_SHA256="$(node -e "try{const fs=require('fs'); const data=JSON.parse(fs.readFileSync(process.argv[1],'utf8')); process.stdout.write(data.artifact?.sha256||'')}catch{}" "$VERSION_JSON")"
  METADATA_GIT_SHA="$(node -e "try{const fs=require('fs'); const data=JSON.parse(fs.readFileSync(process.argv[1],'utf8')); process.stdout.write(data.gitSha||'')}catch{}" "$VERSION_JSON")"
fi
if [ -n "$EXPECTED_SHA256" ] && command -v sha256sum >/dev/null 2>&1; then
  ACTUAL_SHA256="$(sha256sum "$TARBALL" | node -e "let s=''; process.stdin.on('data', c => s += c); process.stdin.on('end', () => process.stdout.write(s.trim().split(/\\s+/)[0] || ''))")"
  if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then
    INNER_FAIL_REASON=inner_checksum_failed
    echo "Package checksum mismatch" >&2
    echo "  expected: $EXPECTED_SHA256" >&2
    echo "  actual:   $ACTUAL_SHA256" >&2
    exit 1
  fi
  echo "Verified package checksum: $ACTUAL_SHA256"
fi

NPM_GLOBAL_ROOT="$(npm root --global --prefix "$INSTALL_DIR" 2>/dev/null || true)"
if [ -z "$NPM_GLOBAL_ROOT" ]; then
  NPM_GLOBAL_ROOT="$INSTALL_DIR/lib/node_modules"
fi

cleanup_stale_npm_package_artifacts() {
  _thkj_cleanup_mode="${1:-stale-only}"
  _thkj_scope_dir="$NPM_GLOBAL_ROOT/@thkj"
  [ -d "$_thkj_scope_dir" ] || return 0

  if [ "$_thkj_cleanup_mode" = "retry" ] || [ "$_thkj_cleanup_mode" = "retry-preserve-legacy" ]; then
    rm -rf "$_thkj_scope_dir/lucky-code"
    if [ "$_thkj_cleanup_mode" = "retry" ]; then
      rm -rf "$_thkj_scope_dir/claude-code"
    fi
    for _thkj_bin in lucky thkj claude; do
      rm -f "$INSTALL_DIR/bin/$_thkj_bin" "$INSTALL_DIR/bin/$_thkj_bin.cmd" "$INSTALL_DIR/bin/$_thkj_bin.ps1" 2>/dev/null || true
    done
  elif [ "$_thkj_cleanup_mode" = "migration-complete" ]; then
    rm -rf "$_thkj_scope_dir/claude-code"
  fi

  for _thkj_stale_dir in "$_thkj_scope_dir"/.claude-code-* "$_thkj_scope_dir"/.lucky-code-*; do
    if [ -e "$_thkj_stale_dir" ] || [ -L "$_thkj_stale_dir" ]; then
      rm -rf "$_thkj_stale_dir"
    fi
  done
}

LEGACY_PACKAGE_DIR="$NPM_GLOBAL_ROOT/@thkj/claude-code"
PACKAGE_DIR="$NPM_GLOBAL_ROOT/@thkj/lucky-code"
MIGRATING_LEGACY_PACKAGE=0
if [ -f "$LEGACY_PACKAGE_DIR/package.json" ] && [ ! -f "$PACKAGE_DIR/package.json" ]; then
  MIGRATING_LEGACY_PACKAGE=1
fi

if { [ -d "$PACKAGE_DIR" ] && [ ! -f "$PACKAGE_DIR/package.json" ]; } ||
   { [ -d "$LEGACY_PACKAGE_DIR" ] && [ ! -f "$LEGACY_PACKAGE_DIR/package.json" ]; }; then
  echo "Found a partial Lucky package installation; cleaning it before retrying npm install..." >&2
  if [ "$MIGRATING_LEGACY_PACKAGE" = "1" ]; then
    cleanup_stale_npm_package_artifacts retry-preserve-legacy
  else
    cleanup_stale_npm_package_artifacts retry
  fi
else
  cleanup_stale_npm_package_artifacts stale-only
fi

install_lucky_package() {
  if [ "$MIGRATING_LEGACY_PACKAGE" = "1" ]; then
    npm install --global --prefix "$INSTALL_DIR" --no-audit --no-fund --silent --force "$TARBALL"
  else
    npm install --global --prefix "$INSTALL_DIR" --no-audit --no-fund --silent "$TARBALL"
  fi
}

if install_lucky_package; then
  :
else
  _thkj_first_npm_status=$?
  echo "npm install failed with exit code $_thkj_first_npm_status; cleaning partial package files and retrying..." >&2
  if [ "$MIGRATING_LEGACY_PACKAGE" = "1" ]; then
    cleanup_stale_npm_package_artifacts retry-preserve-legacy
  else
    cleanup_stale_npm_package_artifacts retry
  fi
  if ! install_lucky_package; then
    INNER_FAIL_REASON=inner_npm_install_failed
    echo "npm install failed for $TARBALL after cleaning partial package files" >&2
    exit 1
  fi
fi

cleanup_stale_npm_package_artifacts stale-only

PACKAGE_DIR="$NPM_GLOBAL_ROOT/@thkj/lucky-code"
PACKAGE_JSON="$PACKAGE_DIR/package.json"
if [ ! -f "$PACKAGE_JSON" ]; then
  PACKAGE_DIR="$INSTALL_DIR/lib/node_modules/@thkj/lucky-code"
  PACKAGE_JSON="$PACKAGE_DIR/package.json"
fi
if [ -f "$PACKAGE_JSON" ]; then
  cleanup_stale_npm_package_artifacts migration-complete
fi
if [ ! -f "$PACKAGE_JSON" ]; then
  PACKAGE_DIR="$NPM_GLOBAL_ROOT/@thkj/claude-code"
  PACKAGE_JSON="$PACKAGE_DIR/package.json"
fi
if [ ! -f "$PACKAGE_JSON" ]; then
  PACKAGE_DIR="$INSTALL_DIR/lib/node_modules/@thkj/claude-code"
  PACKAGE_JSON="$PACKAGE_DIR/package.json"
fi

test_installed_node_pty() {
  _thkj_pkg_dir="$1"
  [ -d "$_thkj_pkg_dir" ] || return 1
  node - "$_thkj_pkg_dir" <<'EOF_NODE_PTY_PROBE' >/dev/null 2>&1
const { createRequire } = require('module')
const path = require('path')
const packageDir = process.argv[2]
const requireFromPackage = createRequire(path.join(packageDir, 'package.json'))
const pty = requireFromPackage('node-pty')
if (!pty || typeof pty.spawn !== 'function') process.exit(2)
EOF_NODE_PTY_PROBE
}

repair_installed_node_pty() {
  _thkj_pkg_dir="$1"
  [ -d "$_thkj_pkg_dir" ] || return 0
  if [ "${THKJ_SKIP_NODE_PTY_REPAIR:-0}" = "1" ]; then
    return 0
  fi
  case "$LUCKY_PLATFORM" in
    linux|wsl|macos) ;;
    *) return 0 ;;
  esac
  if test_installed_node_pty "$_thkj_pkg_dir"; then
    echo "Verified Lucky web terminal dependency: node-pty"
    return 0
  fi

  echo "Lucky web terminal dependency node-pty did not load; rebuilding native addon..." >&2
  if ( cd "$_thkj_pkg_dir" && npm rebuild node-pty --build-from-source --no-audit --no-fund --silent ); then
    if test_installed_node_pty "$_thkj_pkg_dir"; then
      echo "Rebuilt Lucky web terminal dependency: node-pty"
      return 0
    fi
  fi

  echo "node-pty rebuild did not produce a loadable native addon; reinstalling optional dependency..." >&2
  if ( cd "$_thkj_pkg_dir" && npm install --no-save --include=optional --build-from-source --no-audit --no-fund --silent 'node-pty@^1.1.0' ); then
    if test_installed_node_pty "$_thkj_pkg_dir"; then
      echo "Installed Lucky web terminal dependency: node-pty"
      return 0
    fi
  fi

  echo "Warning: Lucky web terminal native dependency node-pty could not be prepared." >&2
  echo "Lucky web will still run, but local terminal sessions may be unavailable on this machine." >&2
  echo "Install Python, make, and a C++ compiler, then run: cd $_thkj_pkg_dir && npm rebuild node-pty --build-from-source" >&2
}

repair_installed_node_pty "$PACKAGE_DIR"

PAYLOAD_BIN_NAME="$(node -e 'const fs = require("fs"); try { const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); const bin = data.bin || {}; for (const name of ["lucky", "thkj", "claude"]) { if (Object.prototype.hasOwnProperty.call(bin, name)) { process.stdout.write(name); break; } } } catch {}' "$PACKAGE_JSON")"
if [ -z "$PAYLOAD_BIN_NAME" ]; then
  for candidate in lucky thkj claude; do
    if [ -x "$INSTALL_DIR/bin/$candidate" ]; then
      PAYLOAD_BIN_NAME="$candidate"
      break
    fi
  done
fi
if [ -z "$PAYLOAD_BIN_NAME" ]; then
  INNER_FAIL_REASON=inner_payload_missing
  echo "Package did not install a supported CLI executable." >&2
  echo "Checked: $INSTALL_DIR/bin/lucky $INSTALL_DIR/bin/thkj $INSTALL_DIR/bin/claude" >&2
  if [ -d "$INSTALL_DIR/bin" ]; then
    ls -la "$INSTALL_DIR/bin" >&2 || true
  fi
  exit 1
fi
PAYLOAD_BIN="$INSTALL_DIR/bin/$PAYLOAD_BIN_NAME"
if [ ! -x "$PAYLOAD_BIN" ]; then
  INNER_FAIL_REASON=inner_payload_not_executable
  echo "Package declared CLI executable '$PAYLOAD_BIN_NAME', but it is not executable at $PAYLOAD_BIN." >&2
  if [ -d "$INSTALL_DIR/bin" ]; then
    ls -la "$INSTALL_DIR/bin" >&2 || true
  fi
  exit 1
fi

if [ ! -x /usr/bin/env ] && [ -n "$NODE_BIN" ]; then
  PAYLOAD_SHEBANG="$(head -n 1 "$PAYLOAD_BIN" 2>/dev/null || true)"
  if [ "$PAYLOAD_SHEBANG" = "#!/usr/bin/env node" ]; then
    PAYLOAD_TMP="$TMP_DIR/payload-bin"
    { printf '#!%s\n' "$NODE_BIN"; tail -n +2 "$PAYLOAD_BIN"; } > "$PAYLOAD_TMP"
    cat "$PAYLOAD_TMP" > "$PAYLOAD_BIN"
    chmod 755 "$PAYLOAD_BIN"
  fi
fi

cat > "$BIN_DIR/$BIN_NAME" <<EOF_WRAPPER
#!$SH_BIN
export THKJ_BRIDGE_MODE="\${THKJ_BRIDGE_MODE:-1}"
export THKJ_BRIDGE_API_BASE_URL="\${THKJ_BRIDGE_API_BASE_URL:-$BRIDGE_API_BASE_URL}"
if [ -d "$NODE_BIN_DIR" ]; then
  case ":\$PATH:" in
    *":$NODE_BIN_DIR:"*) ;;
    *) export PATH="$NODE_BIN_DIR:\$PATH" ;;
  esac
fi
if [ "$NODE_USE_SYSTEM_CA" = "1" ]; then
  case " \${NODE_OPTIONS:-} " in
    *" --use-system-ca "*|*" --use-bundled-ca "*|*" --use-openssl-ca "*) ;;
    *) export NODE_OPTIONS="\${NODE_OPTIONS:+\$NODE_OPTIONS }--use-system-ca" ;;
  esac
fi
# NewAPI / LuckyAPI upstream for direct-mode auth calls (issue #204). Must
# be distinct from THKJ_BRIDGE_API_BASE_URL, otherwise direct mode would
# fall back to the bridge URL — which has no /api/user/login or
# /api/verification routes and 404s silently into "{status:'sent'}" /
# "no auth credential returned".
export THKJ_UPSTREAM_API_BASE_URL="\${THKJ_UPSTREAM_API_BASE_URL:-https://api.luckyapi.chat}"
export THKJ_CHANNEL="\${THKJ_CHANNEL:-$CHANNEL}"
export THKJ_PRODUCT_NAME="\${THKJ_PRODUCT_NAME:-Lucky Code}"
export CLAUDE_CODE_TMPDIR="\${CLAUDE_CODE_TMPDIR:-\$HOME/tmp}"
export TMPDIR="\${TMPDIR:-\$CLAUDE_CODE_TMPDIR}"
# Isolate lucky's config / credentials / sessions / history / etc. from the
# official \`claude\` binary by routing the whole config home into ~/.lucky.
# Upstream CC honors CLAUDE_CONFIG_DIR natively in envUtils.ts:7 (file paths)
# and macOsKeychainHelpers.ts:33 (keychain service name gets a sha256(dir)
# suffix), so a single export covers .credentials.json, .claude-thkj-bridge.json,
# projects/, sessions/, history.jsonl, settings.json, agents/, etc. Closes
# issue #225 Part B (known-issues rows 6 + 10).
#
# User-overridable: anyone who deliberately wants shared state can export
# CLAUDE_CONFIG_DIR=\$HOME/.claude (or anything else) before invoking lucky.
export CLAUDE_CONFIG_DIR="\${CLAUDE_CONFIG_DIR:-\$HOME/.lucky}"
mkdir -p "\$CLAUDE_CODE_TMPDIR"
mkdir -p "\$CLAUDE_CONFIG_DIR"
# Bridge non-credential user-global config to the official \`claude\` home
# (~/.claude) via symlinks so REPL history, settings, memory, agents, and
# plugins stay in sync across both binaries. Credentials, projects/, sessions/,
# and the lucky-only .claude-thkj-bridge.json stay strictly under
# \$CLAUDE_CONFIG_DIR — those are auth- or session-tied and cannot be shared.
#
# Idempotent: only creates a symlink when (a) the source exists in ~/.claude,
# (b) nothing exists at the destination (no real file, no dangling symlink, no
# directory the user has already populated), and (c) CLAUDE_CONFIG_DIR isn't
# already ~/.claude itself. Cheap per-invocation since the common path is
# "all symlinks already present" → just 5 \`test -e\` calls.
#
# One-way: this only auto-bridges on lucky launch. If a user already used
# lucky to build up ~/.lucky/settings.json before ever installing official cc,
# the official cc will not auto-bridge back; the user can \`mv\` and \`ln -s\`
# manually. Documented limitation, #225 follow-up.
if [ "\$CLAUDE_CONFIG_DIR" != "\$HOME/.claude" ]; then
  for _shared_item in history.jsonl settings.json CLAUDE.md agents plugins projects; do
    _shared_src="\$HOME/.claude/\$_shared_item"
    _shared_dst="\$CLAUDE_CONFIG_DIR/\$_shared_item"
    if [ -e "\$_shared_src" ] && [ ! -e "\$_shared_dst" ] && [ ! -L "\$_shared_dst" ]; then
      ln -s "\$_shared_src" "\$_shared_dst" 2>/dev/null || true
    fi
  done
  unset _shared_item _shared_src _shared_dst
fi
if [ "\${1:-}" = "--version" ] || [ "\${1:-}" = "-v" ] || [ "\${1:-}" = "-V" ]; then
  if [ -x "$PAYLOAD_BIN" ]; then
    RAW_VERSION="\$("$PAYLOAD_BIN" --version 2>/dev/null | node -e "let s=''; process.stdin.on('data', c => s += c); process.stdin.on('end', () => { const m = s.match(/(?:Claude Code v)?([0-9]+\\.[0-9]+\\.[0-9][^\\s)]*)/); process.stdout.write(m?.[1] || '$VERSION'); })")"
    echo "\$RAW_VERSION (THKJ Lucky Code)"
  else
    echo "$VERSION (THKJ Lucky Code)"
  fi
  exit 0
fi
if [ "\${1:-}" = "update" ]; then
  export THKJ_BRIDGE_API_BASE_URL="\$THKJ_BRIDGE_API_BASE_URL"
  export THKJ_CHANNEL="$CHANNEL"
  export THKJ_BIN_NAME="$BIN_NAME"
  export THKJ_BIN_DIR="${BIN_DIR}"
  export THKJ_INSTALL_DIR="${INSTALL_DIR}"
  export THKJ_AUTO_UPDATE_SKIP=1
  if command -v curl >/dev/null 2>&1; then
    curl -fsSL "\${THKJ_BRIDGE_API_BASE_URL%/}/install.sh" | sh
  elif command -v wget >/dev/null 2>&1; then
    wget -qO- "\${THKJ_BRIDGE_API_BASE_URL%/}/install.sh" | sh
  else
    echo "Missing required command: curl or wget" >&2
    exit 1
  fi
  exit 0
fi

LUCKY_UPDATE_TELEMETRY_URL="\${LUCKY_UPDATE_TELEMETRY_URL:-https://claude-zh.cn/api/update-event}"
LUCKY_WRAPPER_TELEMETRY_URL="\${LUCKY_WRAPPER_TELEMETRY_URL:-https://claude-zh.cn/api/wrapper-event}"
LUCKY_INSTALL_ID_FILE="${INSTALL_DIR}/.install-id"
LUCKY_FIRST_RUN_FILE="${INSTALL_DIR}/.first-run-done"

lucky_detect_os() {
  case "\$(uname -s 2>/dev/null)" in
    Darwin) printf macos ;;
    Linux)
      if grep -qi microsoft /proc/version 2>/dev/null; then
        printf wsl
      else
        printf linux
      fi
      ;;
    *) printf unknown ;;
  esac
}

lucky_detect_arch() {
  case "\$(uname -m 2>/dev/null)" in
    x86_64|amd64) printf x64 ;;
    aarch64|arm64) printf arm64 ;;
    armv7l|armv6l|arm) printf arm ;;
    i386|i686) printf x86 ;;
    *) printf unknown ;;
  esac
}

lucky_safe_token() {
  printf '%s' "\${1:-}" | tr -c 'A-Za-z0-9._-' '_'
}

lucky_install_id() {
  [ "\${LUCKY_DISABLE_TELEMETRY:-0}" = "1" ] && return 0
  if [ -f "\$LUCKY_INSTALL_ID_FILE" ]; then
    _existing="\$(cat "\$LUCKY_INSTALL_ID_FILE" 2>/dev/null | tr -d '\\r\\n' || true)"
    case "\$_existing" in
      [a-zA-Z0-9]*[a-zA-Z0-9-]*[a-zA-Z0-9]) printf '%s' "\$_existing"; return 0 ;;
    esac
  fi
  _new=""
  if [ -r /proc/sys/kernel/random/uuid ]; then
    _new="\$(cat /proc/sys/kernel/random/uuid 2>/dev/null | tr -d '\\r\\n' || true)"
  fi
  if [ -z "\$_new" ] && command -v uuidgen >/dev/null 2>&1; then
    _new="\$(uuidgen 2>/dev/null | tr -d '\\r\\n' || true)"
  fi
  if [ -z "\$_new" ]; then
    _new="\$(head -c 16 /dev/urandom 2>/dev/null | od -An -tx1 2>/dev/null | tr -d ' \\n' | head -c 32 || true)"
  fi
  [ -n "\$_new" ] || return 0
  ( printf '%s' "\$_new" > "\$LUCKY_INSTALL_ID_FILE" ) 2>/dev/null || true
  printf '%s' "\$_new"
}

# args: event command
# Emits to /api/wrapper-event for wrapper lifecycle signals
# (lucky_wrapper_first_run, lucky_wrapper_launch). Separate from
# lucky_update_event which is for auto-update funnel.
lucky_wrapper_event() {
  [ "\${LUCKY_DISABLE_TELEMETRY:-0}" = "1" ] && return 0
  [ -z "\$LUCKY_WRAPPER_TELEMETRY_URL" ] && return 0
  _we_evt="\$1"
  _we_cmd="\$(lucky_safe_token "\${2:-}")"
  _we_os="\$(lucky_detect_os)"
  _we_arch="\$(lucky_detect_arch)"
  _we_ch="\$(lucky_safe_token "\$THKJ_CHANNEL")"
  if [ -n "\${LUCKY_INSTALL_ID:-}" ]; then
    _we_iid="\$(lucky_safe_token "\$LUCKY_INSTALL_ID")"
  else
    _we_iid="\$(lucky_install_id)"
  fi
  _we_url="\${LUCKY_WRAPPER_TELEMETRY_URL%/}/unix_wrapper/\$_we_os/\$_we_arch/\$_we_evt"
  _we_query="channel=\$_we_ch&installed_version=$VERSION&installed_git_sha=$METADATA_GIT_SHA&command=\$_we_cmd&install_id=\$_we_iid"
  # Background so wrapper exec never blocks on telemetry latency.
  if command -v curl >/dev/null 2>&1; then
    ( curl -fsS --max-time 2 "\$_we_url?\$_we_query" >/dev/null 2>&1 ) &
  elif command -v wget >/dev/null 2>&1; then
    ( wget -qO- --timeout=2 --tries=1 "\$_we_url?\$_we_query" >/dev/null 2>&1 ) &
  fi
}

# args: event reason duration_ms latest_version latest_git_sha
lucky_update_event() {
  [ "\${LUCKY_DISABLE_TELEMETRY:-0}" = "1" ] && return 0
  [ -z "\$LUCKY_UPDATE_TELEMETRY_URL" ] && return 0
  _le_evt="\$1"
  _le_reason="\$(lucky_safe_token "\${2:-}")"
  _le_dur="\$(lucky_safe_token "\${3:-0}")"
  _le_lv="\$(lucky_safe_token "\${4:-}")"
  _le_lgs="\$(lucky_safe_token "\${5:-}")"
  _le_os="\$(lucky_detect_os)"
  _le_arch="\$(lucky_detect_arch)"
  _le_ch="\$(lucky_safe_token "\$THKJ_CHANNEL")"
  _le_iid="\$(lucky_install_id)"
  _le_url="\${LUCKY_UPDATE_TELEMETRY_URL%/}/unix_wrapper/\$_le_os/\$_le_arch/\$_le_evt"
  _le_query="channel=\$_le_ch&current_version=$VERSION&current_git_sha=$METADATA_GIT_SHA&latest_version=\$_le_lv&latest_git_sha=\$_le_lgs&reason=\$_le_reason&duration_ms=\$_le_dur&install_id=\$_le_iid"
  # Background so wrapper exec never blocks on telemetry latency.
  if command -v curl >/dev/null 2>&1; then
    ( curl -fsS --max-time 2 "\$_le_url?\$_le_query" >/dev/null 2>&1 ) &
  elif command -v wget >/dev/null 2>&1; then
    ( wget -qO- --timeout=2 --tries=1 "\$_le_url?\$_le_query" >/dev/null 2>&1 ) &
  fi
}

thkj_auto_update_check() {
  case "\${THKJ_AUTO_UPDATE:-1}" in
    0|false|FALSE|no|NO|off|OFF) return 0 ;;
  esac
  [ "\${THKJ_AUTO_UPDATE_SKIP:-0}" != "1" ] || return 0
  case "\${1:-}" in
    --version|-v|-V|doctor|update|uninstall) return 0 ;;
  esac
  command -v node >/dev/null 2>&1 || return 0

  # Check on every normal launch by default. Operators can set a positive
  # interval to rate-limit metadata requests in managed environments.
  interval="\${THKJ_AUTO_UPDATE_INTERVAL_SECONDS:-0}"
  case "\$interval" in
    ''|*[!0-9]*) interval=0 ;;
  esac
  now="\$(node -e "process.stdout.write(String(Math.floor(Date.now() / 1000)))" 2>/dev/null || echo 0)"
  state_file="${INSTALL_DIR}/.auto-update-check"
  last=0
  if [ -f "\$state_file" ]; then
    last="\$(cat "\$state_file" 2>/dev/null || echo 0)"
  fi
  case "\$last" in
    ''|*[!0-9]*) last=0 ;;
  esac
  if [ "\$interval" -gt 0 ] && [ "\$now" -gt 0 ] && [ "\$last" -gt 0 ] && [ \$((now - last)) -lt "\$interval" ]; then
    return 0
  fi
  printf '%s\n' "\$now" > "\$state_file" 2>/dev/null || true
  lucky_update_event lucky_auto_update_check

  version_endpoint="\${THKJ_BRIDGE_API_BASE_URL%/}/version"
  if [ "\$THKJ_CHANNEL" != "stable" ]; then
    version_endpoint="\${THKJ_BRIDGE_API_BASE_URL%/}/channels/\$THKJ_CHANNEL/version.json"
  fi
  # Bound the version fetch so wrapper exec isn't blocked indefinitely on
  # slow networks. 5s is plenty for a small JSON payload from a CDN-fronted
  # bridge; if it takes longer the auto-update is gracefully skipped this run.
  if command -v curl >/dev/null 2>&1; then
    version_json="\$(curl -fsSL --max-time 5 "\$version_endpoint" 2>/dev/null || true)"
  elif command -v wget >/dev/null 2>&1; then
    version_json="\$(wget -qO- --timeout=5 --tries=1 "\$version_endpoint" 2>/dev/null || true)"
  else
    return 0
  fi
  latest_version="\$(printf '%s' "\$version_json" | node -e "let s=''; process.stdin.on('data', c => s += c); process.stdin.on('end', () => { try { const data = JSON.parse(s || '{}'); process.stdout.write(data.version || ''); } catch {} });" 2>/dev/null || true)"
  latest_git_sha="\$(printf '%s' "\$version_json" | node -e "let s=''; process.stdin.on('data', c => s += c); process.stdin.on('end', () => { try { const data = JSON.parse(s || '{}'); process.stdout.write(data.gitSha || ''); } catch {} });" 2>/dev/null || true)"
  latest_artifact_sha="\$(printf '%s' "\$version_json" | node -e "let s=''; process.stdin.on('data', c => s += c); process.stdin.on('end', () => { try { const data = JSON.parse(s || '{}'); process.stdout.write((data.artifact && data.artifact.sha256) || ''); } catch {} });" 2>/dev/null || true)"
  if [ -z "\$latest_version" ]; then
    lucky_update_event lucky_auto_update_check_failed metadata_unavailable
    return 0
  fi
  if [ "\$latest_version" = "$VERSION" ] && { [ -z "\$latest_git_sha" ] || [ -z "$METADATA_GIT_SHA" ] || [ "\$latest_git_sha" = "$METADATA_GIT_SHA" ]; } && { [ -z "\$latest_artifact_sha" ] || [ -z "$EXPECTED_SHA256" ] || [ "\$latest_artifact_sha" = "$EXPECTED_SHA256" ]; }; then
    lucky_update_event lucky_auto_update_up_to_date "" "" "\$latest_version" "\$latest_git_sha"
    return 0
  fi

  lucky_update_event lucky_auto_update_available "" "" "\$latest_version" "\$latest_git_sha"
  export THKJ_BRIDGE_API_BASE_URL="\$THKJ_BRIDGE_API_BASE_URL"
  export THKJ_CHANNEL="$CHANNEL"
  export THKJ_BIN_NAME="$BIN_NAME"
  export THKJ_BIN_DIR="${BIN_DIR}"
  export THKJ_INSTALL_DIR="${INSTALL_DIR}"
  export THKJ_AUTO_UPDATE_SKIP=1
  lucky_update_event lucky_auto_update_start "" "" "\$latest_version" "\$latest_git_sha"
  printf '%s\n' "[lucky] Update available: installing \$latest_version before startup..." >&2
  _update_start_ms="\$(node -e "process.stdout.write(String(Date.now()))" 2>/dev/null || echo 0)"
  _update_status=0
  _update_fail_reason=""
  # Download the installer to a tempfile FIRST so we can distinguish a
  # download failure (curl/wget exit code) from an installer exit code.
  # A pipeline like \`curl | sh\` would mask download failure: sh reads
  # empty stdin and exits 0, polluting the success rate.
  _update_payload="${INSTALL_DIR}/.auto-update-payload.sh"
  if command -v curl >/dev/null 2>&1; then
    if curl -fsSL "\${THKJ_BRIDGE_API_BASE_URL%/}/install.sh" -o "\$_update_payload" 2>/dev/null; then
      sh "\$_update_payload"
      _update_status=\$?
    else
      _update_status=1
      _update_fail_reason=download_failed
    fi
  elif command -v wget >/dev/null 2>&1; then
    if wget -qO "\$_update_payload" "\${THKJ_BRIDGE_API_BASE_URL%/}/install.sh" 2>/dev/null; then
      sh "\$_update_payload"
      _update_status=\$?
    else
      _update_status=1
      _update_fail_reason=download_failed
    fi
  else
    _update_status=127
    _update_fail_reason=no_downloader
  fi
  rm -f "\$_update_payload" 2>/dev/null || true
  _update_end_ms="\$(node -e "process.stdout.write(String(Date.now()))" 2>/dev/null || echo 0)"
  _update_dur_ms=0
  if [ "\$_update_start_ms" -gt 0 ] && [ "\$_update_end_ms" -ge "\$_update_start_ms" ]; then
    _update_dur_ms=\$((_update_end_ms - _update_start_ms))
  fi
  if [ "\$_update_status" = "0" ]; then
    lucky_update_event lucky_auto_update_success "" "\$_update_dur_ms" "\$latest_version" "\$latest_git_sha"
    printf '%s\n' "[lucky] Update completed. Continuing startup..." >&2
  else
    if [ -z "\$_update_fail_reason" ]; then
      _update_fail_reason="installer_exit_\$_update_status"
    fi
    lucky_update_event lucky_auto_update_failed "\$_update_fail_reason" "\$_update_dur_ms" "\$latest_version" "\$latest_git_sha"
    printf '%s\n' "[lucky] Update failed (\$_update_fail_reason). Continuing with installed version..." >&2
  fi
}

thkj_uninstall() {
  _yes=0
  _remove_config=0
  shift
  for _arg in "\$@"; do
    case "\$_arg" in
      --yes|-y) _yes=1 ;;
      --remove-config|--remove-data) _remove_config=1 ;;
      --keep-config|--keep-data) _remove_config=0 ;;
      --help|-h)
        cat <<'EOF_UNINSTALL_HELP'
Usage: lucky uninstall [--yes] [--remove-config|--keep-config]

Removes the Lucky Code launcher and installed program files. By default,
chat records and user configuration are kept. Use --remove-config to also
delete Lucky user data.
EOF_UNINSTALL_HELP
        exit 0
        ;;
      *)
        printf '%s\n' "Unknown uninstall option: \$_arg" >&2
        exit 2
        ;;
    esac
  done

  LUCKY_INSTALL_ID="\$(lucky_install_id)"
  export LUCKY_INSTALL_ID
  lucky_wrapper_event lucky_wrapper_uninstall_start uninstall

  printf '%s\n' "Lucky Code uninstall"
  printf '%s\n' ""
  printf '%s\n' "Program files: ${INSTALL_DIR}"
  printf '%s\n' "Launcher:      ${BIN_DIR}/${BIN_NAME}"
  printf '%s\n' "User data:     \$HOME/.lucky"
  printf '%s\n' "Web data:      \$HOME/.lucky-code"
  printf '%s\n' ""

  if [ "\$_yes" != "1" ]; then
    printf '%s' "Remove Lucky Code program files? [y/N] "
    read _confirm || _confirm=""
    case "\$_confirm" in
      y|Y|yes|YES|Yes) ;;
      *)
        lucky_wrapper_event lucky_wrapper_uninstall_cancelled uninstall
        printf '%s\n' "Uninstall cancelled."
        exit 1
        ;;
    esac

    printf '%s' "Also remove Lucky chat records and user configuration? [y/N] "
    read _config_confirm || _config_confirm=""
    case "\$_config_confirm" in
      y|Y|yes|YES|Yes) _remove_config=1 ;;
      *) _remove_config=0 ;;
    esac
  fi

  _failed=0
  rm -f "${BIN_DIR}/${BIN_NAME}" 2>/dev/null || _failed=1
  rm -rf "${INSTALL_DIR}" 2>/dev/null || _failed=1
  if [ "\$_remove_config" = "1" ]; then
    rm -rf "\$HOME/.lucky" "\$HOME/.lucky-code" 2>/dev/null || _failed=1
  fi

  if [ "\$_failed" = "0" ]; then
    lucky_wrapper_event lucky_wrapper_uninstall_success uninstall
    printf '%s\n' "Lucky Code has been uninstalled."
    if [ "\$_remove_config" = "1" ]; then
      printf '%s\n' "Lucky chat records and user configuration were removed."
    else
      printf '%s\n' "Lucky chat records and user configuration were kept."
    fi
    exit 0
  fi

  lucky_wrapper_event lucky_wrapper_uninstall_failed uninstall
  printf '%s\n' "Lucky Code uninstall failed. Check file permissions and try again." >&2
  exit 1
}

if [ "\${1:-}" = "doctor" ]; then
  echo "Lucky Code doctor"
  echo ""
  echo "Channel: \$THKJ_CHANNEL"
  echo "lucky:  \$THKJ_BRIDGE_API_BASE_URL"
  echo "newapi: \$THKJ_UPSTREAM_API_BASE_URL"
  echo "Temp:   \$CLAUDE_CODE_TMPDIR"
  echo "$BIN_NAME:   $BIN_DIR/$BIN_NAME"
  echo "payload: $PAYLOAD_BIN"
  CURRENT_THKJ="\$(command -v "$BIN_NAME" 2>/dev/null || true)"
  if [ -n "\$CURRENT_THKJ" ] && [ "\$CURRENT_THKJ" != "$BIN_DIR/$BIN_NAME" ]; then
    echo "path $BIN_NAME:   \$CURRENT_THKJ (shadowing wrapper)"
  fi
  echo "node:   \$(command -v node 2>/dev/null || echo not found)"
  echo "npm:    \$(command -v npm 2>/dev/null || echo not found)"
  if [ -x "$PAYLOAD_BIN" ]; then
    echo "version: \$("$PAYLOAD_BIN" --version 2>/dev/null || echo failed)"
  else
    echo "version: installed CLI not found at $PAYLOAD_BIN"
  fi
  BRIDGE_HELLO_JSON=""
  BRIDGE_VERSION_JSON=""
  BRIDGE_VERSION_ENDPOINT="\${THKJ_BRIDGE_API_BASE_URL%/}/version"
  if [ "\$THKJ_CHANNEL" != "stable" ]; then
    BRIDGE_VERSION_ENDPOINT="\${THKJ_BRIDGE_API_BASE_URL%/}/channels/\$THKJ_CHANNEL/version.json"
  fi
  if command -v curl >/dev/null 2>&1; then
    BRIDGE_HELLO_JSON="\$(curl -fsSL "\${THKJ_BRIDGE_API_BASE_URL%/}/api/hello" 2>/dev/null || true)"
    if [ -n "\$BRIDGE_HELLO_JSON" ]; then
      echo "lucky health: ok"
      BRIDGE_VERSION_JSON="\$(curl -fsSL "\$BRIDGE_VERSION_ENDPOINT" 2>/dev/null || true)"
    else
      echo "lucky health: failed"
    fi
  elif command -v wget >/dev/null 2>&1; then
    BRIDGE_HELLO_JSON="\$(wget -qO- "\${THKJ_BRIDGE_API_BASE_URL%/}/api/hello" 2>/dev/null || true)"
    if [ -n "\$BRIDGE_HELLO_JSON" ]; then
      echo "lucky health: ok"
      BRIDGE_VERSION_JSON="\$(wget -qO- "\$BRIDGE_VERSION_ENDPOINT" 2>/dev/null || true)"
    else
      echo "lucky health: failed"
    fi
  else
    echo "lucky health: skipped; curl/wget missing"
  fi
  if [ -n "\$BRIDGE_HELLO_JSON\$BRIDGE_VERSION_JSON" ]; then
    BRIDGE_HELLO_JSON="\$BRIDGE_HELLO_JSON" BRIDGE_VERSION_JSON="\$BRIDGE_VERSION_JSON" node -e "const parse = value => { try { return JSON.parse(value || '{}') } catch { return {} } }; const hello = parse(process.env.BRIDGE_HELLO_JSON); const data = parse(process.env.BRIDGE_VERSION_JSON); if (hello.channel) console.log('lucky bridge channel: ' + hello.channel); if (hello.preview !== undefined) console.log('lucky bridge preview: ' + (hello.preview ? 'yes' : 'no')); if (hello.public_base_url) console.log('lucky bridge base: ' + hello.public_base_url); if (data.channel) console.log('lucky artifact channel: ' + data.channel); if (data.version) console.log('lucky version: ' + data.version); if (data.gitRef) console.log('git ref: ' + data.gitRef); if (data.gitSha) console.log('git sha: ' + data.gitSha); if (data.runId) console.log('workflow run: ' + data.runId); if (data.artifact?.sha256) console.log('package sha256: ' + data.artifact.sha256); if (data.artifact?.size) console.log('package size: ' + data.artifact.size);" 2>/dev/null || true
  fi
  exit 0
fi
if [ "\${1:-}" = "uninstall" ]; then
  thkj_uninstall "\$@"
fi
case "\${1:-}" in
  --version|-v|-V|doctor|update|uninstall) ;;  # skip lifecycle telemetry for side commands
  *)
    lucky_wrapper_event lucky_wrapper_launch "\${1:-}"
    if [ ! -f "\$LUCKY_FIRST_RUN_FILE" ]; then
      lucky_wrapper_event lucky_wrapper_first_run "\${1:-}"
      ( : > "\$LUCKY_FIRST_RUN_FILE" ) 2>/dev/null || true
    fi
    ;;
esac

# Keep startup responsive: update checks and installation run in the background.
# The current invocation uses the payload captured above; a later invocation picks
# up the refreshed installation after the background updater finishes.
( thkj_auto_update_check "\${1:-}" >/dev/null 2>&1 ) &
exec "$PAYLOAD_BIN" "\$@"
EOF_WRAPPER
chmod 755 "$BIN_DIR/$BIN_NAME"
lucky_inner_install_event inner_wrapper_generated

PATH_PROFILE_FILE=""
PATH_PROFILE_STATUS=""

shell_profile_for_path_update() {
  if [ -n "${THKJ_SHELL_PROFILE:-}" ]; then
    printf '%s' "$THKJ_SHELL_PROFILE"
    return 0
  fi

  _thkj_shell_name="${SHELL:-}"
  _thkj_shell_name="${_thkj_shell_name##*/}"
  case "$_thkj_shell_name" in
    zsh) printf '%s/.zshrc' "$HOME" ;;
    bash)
      if [ "$LUCKY_PLATFORM" = "macos" ]; then
        printf '%s/.bash_profile' "$HOME"
      else
        printf '%s/.bashrc' "$HOME"
      fi
      ;;
    fish) printf '%s/.config/fish/config.fish' "$HOME" ;;
    *)
      if [ "$LUCKY_PLATFORM" = "macos" ]; then
        printf '%s/.zshrc' "$HOME"
      else
        printf '%s/.profile' "$HOME"
      fi
      ;;
  esac
}

ensure_bin_dir_on_path() {
  if path_contains_dir "$BIN_DIR"; then
    PATH_PROFILE_STATUS=current
    return 0
  fi
  if [ "${THKJ_SKIP_PATH_UPDATE:-0}" = "1" ]; then
    PATH_PROFILE_STATUS=skipped
    return 0
  fi

  PATH_PROFILE_FILE="$(shell_profile_for_path_update)"
  [ -n "$PATH_PROFILE_FILE" ] || { PATH_PROFILE_STATUS=failed; return 0; }
  _thkj_profile_dir="$(dirname "$PATH_PROFILE_FILE")"
  mkdir -p "$_thkj_profile_dir" 2>/dev/null || { PATH_PROFILE_STATUS=failed; return 0; }

  if [ -f "$PATH_PROFILE_FILE" ] && grep -F "$BIN_DIR" "$PATH_PROFILE_FILE" >/dev/null 2>&1; then
    PATH_PROFILE_STATUS=already
    return 0
  fi

  _thkj_shell_name="${SHELL:-}"
  _thkj_shell_name="${_thkj_shell_name##*/}"
  if [ "$_thkj_shell_name" = "fish" ]; then
    {
      printf '\n'
      printf '%s\n' '# Added by Lucky installer'
      printf '%s\n' "if not contains -- \"$BIN_DIR\" \$PATH"
      printf '%s\n' "    set -gx PATH \"$BIN_DIR\" \$PATH"
      printf '%s\n' 'end'
    } >> "$PATH_PROFILE_FILE" 2>/dev/null || { PATH_PROFILE_STATUS=failed; return 0; }
  else
    {
      printf '\n'
      printf '%s\n' '# Added by Lucky installer'
      printf '%s\n' 'case ":$PATH:" in'
      printf '%s\n' "  *\":$BIN_DIR:\"*) ;;"
      printf '%s\n' "  *) export PATH=\"$BIN_DIR:\$PATH\" ;;"
      printf '%s\n' 'esac'
    } >> "$PATH_PROFILE_FILE" 2>/dev/null || { PATH_PROFILE_STATUS=failed; return 0; }
  fi
  PATH_PROFILE_STATUS=updated
}

ensure_bin_dir_on_path

# Persist the install_id so the wrapper (and later auto-update reruns of
# this script) sees the same anonymous id. Wrapper's lucky_install_id
# helper reads this file lazily; without it the wrapper would mint its
# own id and the install↔launch funnel join would break.
( printf '%s' "$LUCKY_INSTALL_ID" > "$INSTALL_DIR/.install-id" ) 2>/dev/null || true

LEGACY_CLAUDE_NOTICE=""
if [ -e "$BIN_DIR/claude" ] && grep -q 'THKJ_BRIDGE_MODE' "$BIN_DIR/claude" 2>/dev/null; then
  cat > "$BIN_DIR/claude" <<EOF_LEGACY_CLAUDE
#!$SH_BIN
echo "THKJ no longer installs the claude command. Use lucky instead." >&2
echo "If you want official Claude Code, reinstall it separately so claude points to the official CLI." >&2
exit 127
EOF_LEGACY_CLAUDE
  chmod 755 "$BIN_DIR/claude"
  LEGACY_CLAUDE_NOTICE="
Legacy notice:
  Replaced old THKJ-owned $BIN_DIR/claude with a migration notice. Use lucky for THKJ Lucky Code.
  Reinstall official Claude Code separately if you want claude to launch the official CLI."
elif [ -e "$BIN_DIR/claude" ]; then
  LEGACY_CLAUDE_NOTICE="
Legacy notice:
  $BIN_DIR/claude exists and does not look installer-owned, so this installer left it untouched."
fi

PATH_HINT=""
CURRENT_THKJ="$(command -v "$BIN_NAME" 2>/dev/null || true)"
if [ "$CURRENT_THKJ" != "$BIN_DIR/$BIN_NAME" ]; then
  case "$PATH_PROFILE_STATUS" in
    updated)
      PATH_HINT="
PATH notice:
  Added $BIN_DIR to PATH in:
    $PATH_PROFILE_FILE

  Restart your terminal, or run this now:
    export PATH=\"$BIN_DIR:\$PATH\"

  Current $BIN_NAME:   ${CURRENT_THKJ:-not found}"
      ;;
    already)
      PATH_HINT="
PATH notice:
  $BIN_DIR is already listed in:
    $PATH_PROFILE_FILE

  Restart your terminal, or run this now:
    export PATH=\"$BIN_DIR:\$PATH\"

  Current $BIN_NAME:   ${CURRENT_THKJ:-not found}"
      ;;
    skipped)
      PATH_HINT="
PATH notice:
  THKJ_SKIP_PATH_UPDATE=1 is set, so the installer did not edit your shell profile.
  Add this manually:
    export PATH=\"$BIN_DIR:\$PATH\"

  Current $BIN_NAME:   ${CURRENT_THKJ:-not found}"
      ;;
    *)
      PATH_HINT="
PATH notice:
  Put $BIN_DIR before other $BIN_NAME installs:
    export PATH=\"$BIN_DIR:\$PATH\"

  Current $BIN_NAME:   ${CURRENT_THKJ:-not found}"
      ;;
  esac
fi

cat <<EOF
Lucky Code installed.

Version: $VERSION
Channel: $CHANNEL
Binary:  $BIN_DIR/$BIN_NAME
Payload: $PAYLOAD_BIN
lucky:   $BRIDGE_API_BASE_URL
$PATH_HINT
$LEGACY_CLAUDE_NOTICE

Next steps:
  $BIN_NAME
  Then type /login in the interactive session.

Update later:
  $BIN_NAME update

Uninstall:
  $BIN_NAME uninstall

交流 / 反馈 · QQ 群: 936493004
EOF
