#!/usr/bin/env bash
# MiniMax Code CLI bootstrap installer for macOS, Linux, and WSL.
#
# Stable user command:
#   curl -fsSL <INSTALLER_URL>/install.sh | bash

set -euo pipefail

MCODE_PACKAGE_NAME='@minimax-ai/code'
MCODE_INSTALL_DIR="${MCODE_INSTALL_DIR:-$HOME/.minimax-code}"
MCODE_NO_MODIFY_PATH="${MCODE_NO_MODIFY_PATH:-}"
MCODE_DOWNLOAD_MIRROR="${MCODE_DOWNLOAD_MIRROR:-}"

MCODE_PACKAGE_VERSION=''

NODE_VERSION='24.19.0'
case "$MCODE_DOWNLOAD_MIRROR" in
  '')
    DEFAULT_NPM_REGISTRY='https://registry.npmjs.org/'
    DEFAULT_NODE_DIST_BASE="https://nodejs.org/dist/v${NODE_VERSION}"
    DEFAULT_NODE_FALLBACK_DIST_BASE=''
    DEFAULT_BETTER_SQLITE3_BINARY_HOST_MIRROR=''
    ;;
  global)
    DEFAULT_NPM_REGISTRY='https://registry.npmjs.org/'
    DEFAULT_NODE_DIST_BASE="https://nodejs.org/dist/v${NODE_VERSION}"
    DEFAULT_NODE_FALLBACK_DIST_BASE=''
    DEFAULT_BETTER_SQLITE3_BINARY_HOST_MIRROR=''
    ;;
  cn)
    DEFAULT_NPM_REGISTRY='https://registry.npmmirror.com/'
    DEFAULT_NODE_DIST_BASE="https://npmmirror.com/mirrors/node/v${NODE_VERSION}"
    DEFAULT_NODE_FALLBACK_DIST_BASE="https://nodejs.org/dist/v${NODE_VERSION}"
    DEFAULT_BETTER_SQLITE3_BINARY_HOST_MIRROR='https://npmmirror.com/mirrors/better-sqlite3'
    ;;
  *)
    printf 'error: MCODE_DOWNLOAD_MIRROR must be cn, global, or empty\n' >&2
    exit 1
    ;;
esac
MCODE_NPM_REGISTRY="${MCODE_NPM_REGISTRY:-$DEFAULT_NPM_REGISTRY}"
case "${MCODE_NPM_REGISTRY%/}" in
  https://registry.npmjs.org|https://registry.npmmirror.com)
    MCODE_NPM_REGISTRY="${MCODE_NPM_REGISTRY%/}/"
    ;;
  *)
    printf 'error: MCODE_NPM_REGISTRY must be registry.npmjs.org or registry.npmmirror.com\n' >&2
    exit 1
    ;;
esac
NODE_DIST_BASE="${MCODE_NODE_DIST_BASE:-$DEFAULT_NODE_DIST_BASE}"
NODE_FALLBACK_DIST_BASE="${MCODE_NODE_FALLBACK_DIST_BASE:-$DEFAULT_NODE_FALLBACK_DIST_BASE}"
MCODE_BETTER_SQLITE3_BINARY_HOST_MIRROR="${MCODE_BETTER_SQLITE3_BINARY_HOST_MIRROR:-${npm_config_better_sqlite3_binary_host_mirror:-$DEFAULT_BETTER_SQLITE3_BINARY_HOST_MIRROR}}"

TEMP_ROOT=''
PATH_UPDATED_FILE=''
NODE_RUNTIME_KIND=''
NATIVE_BUILD_TOOLS_MISSING=''
NATIVE_BUILD_TOOLS_HINT=''
STAGING_PREFIX=''
INSTALL_LOCK_CLAIM=''
INSTALL_ACTION='fresh'
INSTALLED_VERSION=''

INSTALL_STAGE_TOTAL=5

have() { command -v "$1" >/dev/null 2>&1; }

supports_color() {
  [[ -t 1 && "${TERM:-}" != 'dumb' && -z "${NO_COLOR:-}" ]]
}

terminal_supports_unicode() {
  local locale_name="${LC_ALL:-${LC_CTYPE:-${LANG:-}}}"
  [[ "$locale_name" =~ [Uu][Tt][Ff]-?8 ]]
}

log() {
  if supports_color; then
    printf '\033[1;36m==>\033[0m %s\n' "$*"
  else
    printf '==> %s\n' "$*"
  fi
}

stage() {
  local number="$1"
  shift
  log "[$number/$INSTALL_STAGE_TOTAL] $*"
}

warn() { printf 'warning: %s\n' "$*" >&2; }

die() {
  printf 'error: %s\n' "$*" >&2
  exit 1
}

usage() {
  cat <<'EOF'
Install MiniMax Code CLI, including an isolated Node.js 24 runtime when needed.

Usage:
  curl -fsSL <INSTALLER_URL>/install.sh | bash
  ./install.sh

Environment overrides:
  MCODE_INSTALL_DIR         User install directory (default: ~/.minimax-code).
  MCODE_NO_MODIFY_PATH      Do not update the user's shell PATH when non-empty.
  NO_COLOR                  Disable ANSI colors.
  MCODE_DOWNLOAD_MIRROR     Set to cn for npmmirror Node/npm/native downloads, or
                            global to explicitly select the official sources.
  MCODE_NPM_REGISTRY        Override the npm registry selected by the mirror mode.
  MCODE_NODE_DIST_BASE      Node.js version directory mirror used when a managed
                            runtime is needed (default: nodejs.org).
  MCODE_NODE_FALLBACK_DIST_BASE
                            Override the automatic managed Node fallback mirror.

The installer does not require a preinstalled Node.js or npm and does not use sudo.
The isolated runtime remains private to MCode and is not added to the user's PATH.
EOF
}

cleanup() {
  if [[ -n "$TEMP_ROOT" && -d "$TEMP_ROOT" ]]; then
    rm -rf -- "$TEMP_ROOT"
  fi
  if [[ -n "$STAGING_PREFIX" && -d "$STAGING_PREFIX" ]]; then
    rm -rf -- "$STAGING_PREFIX"
  fi
  if [[ -n "$INSTALL_LOCK_CLAIM" ]]; then
    rm -f -- "$INSTALL_LOCK_CLAIM"
  fi
  INSTALL_LOCK_CLAIM=''
}
trap cleanup EXIT
trap 'exit 130' HUP INT TERM

case "${1:-}" in
  -h|--help)
    usage
    exit 0
    ;;
  '') ;;
  *) die "unknown option: $1" ;;
esac

show_intro() {
  if [[ -t 1 && "${TERM:-}" != 'dumb' ]] && terminal_supports_unicode; then
    if supports_color; then printf '\033[38;5;117m'; fi
    printf '%s\n' \
      '███╗   ███╗' \
      '████╗ ████║' \
      '██╔████╔██║' \
      '██║╚██╔╝██║' \
      '██║ ╚═╝ ██║' \
      '╚═╝     ╚═╝  MiniMax Code'
    if supports_color; then printf '\033[0m'; fi
  else
    printf 'MINIMAX CODE\n'
  fi
  printf '\n'
}

acquire_install_lock() {
  local lock_directory claim claim_name contender contender_name contender_pid value
  local max_ticket=0 ticket=0
  lock_directory="$MCODE_INSTALL_DIR/.mcode-update.lock"
  mkdir -p "$lock_directory"
  claim_name="$$-${RANDOM:-0}-${RANDOM:-0}.claim"
  claim="$lock_directory/$claim_name"
  ( set -C; printf 'choosing\n' >"$claim" ) 2>/dev/null || \
    die 'could not create a unique MiniMax Code updater claim.'
  INSTALL_LOCK_CLAIM="$claim"

  for contender in "$lock_directory"/*.claim; do
    [[ -e "$contender" ]] || continue
    contender_name="$(basename "$contender")"
    contender_pid="${contender_name%%-*}"
    [[ "$contender_pid" =~ ^[0-9]+$ ]] || continue
    kill -0 "$contender_pid" 2>/dev/null || continue
    value="$(tr -d '\r\n' <"$contender" 2>/dev/null || true)"
    if [[ "$value" =~ ^[0-9]+$ ]] && (( value > max_ticket )); then max_ticket="$value"; fi
  done
  ticket=$((max_ticket + 1))
  printf '%s\n' "$ticket" >"${claim}.tmp"
  mv -f "${claim}.tmp" "$claim"

  for contender in "$lock_directory"/*.claim; do
    [[ -e "$contender" && "$contender" != "$claim" ]] || continue
    contender_name="$(basename "$contender")"
    contender_pid="${contender_name%%-*}"
    [[ "$contender_pid" =~ ^[0-9]+$ ]] || continue
    kill -0 "$contender_pid" 2>/dev/null || continue
    value="$(tr -d '\r\n' <"$contender" 2>/dev/null || true)"
    if [[ ! "$value" =~ ^[0-9]+$ ]] || (( value < ticket )) || \
      { (( value == ticket )) && (( contender_pid < $$ )); } || \
      { (( value == ticket && contender_pid == $$ )) && [[ "$contender_name" < "$claim_name" ]]; }; then
      rm -f -- "$claim"
      INSTALL_LOCK_CLAIM=''
      die 'another MiniMax Code install or update is already running; wait for it to finish and retry.'
    fi
  done
}

assert_no_legacy_update_transaction() {
  if [[ -e "$MCODE_INSTALL_DIR/.mcode-update-pending.json" ]]; then
    die 'a legacy MCode update is already staged; exit the older MCode sessions once so it can finish, then rerun this installer to migrate to non-blocking updates.'
  fi
}

download() {
  local url="$1" destination="$2" detect_slow="${3:-}" retry_count=3 wget_tries=3
  if [[ -n "$detect_slow" ]]; then
    retry_count=0
    wget_tries=1
  fi
  if have curl; then
    if [[ -n "$detect_slow" && -t 1 ]]; then
      curl --fail --location --retry "$retry_count" --connect-timeout 15 \
        --speed-limit 32768 --speed-time 30 --progress-bar \
        --output "$destination" "$url"
    elif [[ -n "$detect_slow" ]]; then
      curl --fail --location --retry "$retry_count" --connect-timeout 15 \
        --speed-limit 32768 --speed-time 30 --silent --show-error \
        --output "$destination" "$url"
    elif [[ -t 1 ]]; then
      curl --fail --location --retry "$retry_count" --connect-timeout 15 --progress-bar \
        --output "$destination" "$url"
    else
      curl --fail --location --retry "$retry_count" --connect-timeout 15 --silent --show-error \
        --output "$destination" "$url"
    fi
  elif have wget; then
    wget --quiet --tries="$wget_tries" --timeout=15 --output-document="$destination" "$url"
  else
    die 'curl or wget is required to download MiniMax Code.'
  fi
}

sha256_file() {
  local file="$1"
  if have shasum; then
    shasum -a 256 "$file" | awk '{print $1}'
  elif have sha256sum; then
    sha256sum "$file" | awk '{print $1}'
  elif have openssl; then
    openssl dgst -sha256 "$file" | awk '{print $NF}'
  else
    die 'shasum, sha256sum, or openssl is required to verify downloads.'
  fi
}

verify_sha256() {
  local file="$1" expected="$2" label="$3" actual actual_lower expected_lower
  [[ "$expected" =~ ^[0-9a-fA-F]{64}$ ]] || die "invalid expected SHA-256 for $label"
  actual="$(sha256_file "$file")"
  actual_lower="$(printf '%s' "$actual" | tr '[:upper:]' '[:lower:]')"
  expected_lower="$(printf '%s' "$expected" | tr '[:upper:]' '[:lower:]')"
  [[ "$actual_lower" == "$expected_lower" ]] || \
    die "$label checksum mismatch: expected $expected, got $actual"
}

download_verified() {
  local url="$1" destination="$2" expected="$3" label="$4" fallback_url="${5:-}"
  local attempt actual actual_lower candidate expected_lower max_attempts size
  [[ "$expected" =~ ^[0-9a-fA-F]{64}$ ]] || die "invalid expected SHA-256 for $label"
  expected_lower="$(printf '%s' "$expected" | tr '[:upper:]' '[:lower:]')"

  for candidate in "$url" "$fallback_url"; do
    [[ -n "$candidate" ]] || continue
    if [[ "$candidate" != "$url" ]]; then
      warn "$label primary download was unavailable or too slow; trying verified fallback $candidate"
    fi
    max_attempts=3
    if [[ "$candidate" == "$url" && -n "$fallback_url" ]]; then
      max_attempts=1
    fi
    attempt=1
    while (( attempt <= max_attempts )); do
      if download "$candidate" "$destination" "$fallback_url"; then
        actual="$(sha256_file "$destination")"
        actual_lower="$(printf '%s' "$actual" | tr '[:upper:]' '[:lower:]')"
        if [[ "$actual_lower" == "$expected_lower" ]]; then
          return 0
        fi
        size="$(wc -c <"$destination" | tr -d '[:space:]')"
        warn "$label checksum mismatch (attempt $attempt/$max_attempts): expected $expected, got $actual; downloaded $size bytes from $candidate"
      else
        warn "$label download failed or stayed below 32 KiB/s for 30 seconds (attempt $attempt/$max_attempts): $candidate"
      fi
      rm -f -- "$destination"
      attempt=$((attempt + 1))
    done
  done

  die "$label download failed validation after exhausting the configured sources. Check proxy, VPN, CDN, or security software and retry."
}

detect_target() {
  local os arch
  case "$(uname -s)" in
    Darwin) os='darwin' ;;
    Linux) os='linux' ;;
    MINGW*|MSYS*|CYGWIN*)
      die 'Windows is not supported by install.sh; run the install.ps1 command in PowerShell.'
      ;;
    *) die "unsupported operating system: $(uname -s)" ;;
  esac

  case "$(uname -m)" in
    x86_64|amd64) arch='x64' ;;
    arm64|aarch64) arch='arm64' ;;
    *) die "unsupported architecture: $(uname -m)" ;;
  esac

  if [[ "$os" == 'darwin' && "$arch" == 'x64' ]] && \
    [[ "$(sysctl -n sysctl.proc_translated 2>/dev/null || true)" == '1' ]]; then
    arch='arm64'
  fi

  if [[ "$os" == 'linux' ]] && \
    { [[ -f /lib/libc.musl-x86_64.so.1 ]] || [[ -f /lib/libc.musl-aarch64.so.1 ]] || \
      (have ldd && ldd /bin/ls 2>&1 | grep -q musl); }; then
    die 'Alpine/musl Linux is not supported by this beta installer.'
  fi

  TARGET_OS="$os"
  TARGET_ARCH="$arch"
  log "Detected target: ${TARGET_OS}-${TARGET_ARCH}"
}

detect_native_build_tools() {
  [[ "$TARGET_OS" == 'linux' ]] || return 0

  local missing=() privilege_prefix=''
  have make || missing+=('make')
  if ! have c++ && ! have g++; then
    missing+=('a C++ compiler')
  fi
  if ! have python3 && \
    ! { have python && python -c 'import sys; raise SystemExit(sys.version_info.major != 3)' >/dev/null 2>&1; }; then
    missing+=('Python 3')
  fi
  [[ "${#missing[@]}" -gt 0 ]] || return 0

  printf -v NATIVE_BUILD_TOOLS_MISSING '%s, ' "${missing[@]}"
  NATIVE_BUILD_TOOLS_MISSING="${NATIVE_BUILD_TOOLS_MISSING%, }"
  if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
    if have sudo; then
      privilege_prefix='sudo '
    else
      privilege_prefix='run as root: '
    fi
  fi

  if have apt-get; then
    NATIVE_BUILD_TOOLS_HINT="${privilege_prefix}apt-get install -y build-essential python3"
  elif have dnf; then
    NATIVE_BUILD_TOOLS_HINT="${privilege_prefix}dnf install -y make gcc gcc-c++ python3"
  elif have yum; then
    NATIVE_BUILD_TOOLS_HINT="${privilege_prefix}yum install -y make gcc gcc-c++ python3"
  elif have zypper; then
    NATIVE_BUILD_TOOLS_HINT="${privilege_prefix}zypper install -y make gcc gcc-c++ python3"
  else
    NATIVE_BUILD_TOOLS_HINT='Install make, a C++ compiler, and Python 3 with your system package manager.'
  fi

  warn "Native SQLite may need local compilation, but this system is missing: ${NATIVE_BUILD_TOOLS_MISSING}."
  warn "If the prebuilt binary is unavailable, install the tools first: ${NATIVE_BUILD_TOOLS_HINT}"
}

print_native_build_tools_help() {
  [[ -n "$NATIVE_BUILD_TOOLS_MISSING" ]] || return 0
  warn "Missing native build tools: ${NATIVE_BUILD_TOOLS_MISSING}."
  warn "Install them and rerun this same command: ${NATIVE_BUILD_TOOLS_HINT}"
}

node_archive_sha256() {
  case "$1" in
    node-v24.19.0-darwin-arm64.tar.gz) printf '%s' '8294b7aa9b03997481c06babf1e8b270c859358f27da57a11509afe537ac381d' ;;
    node-v24.19.0-darwin-x64.tar.gz) printf '%s' 'd1b5e999db158c62fe8f7267a4476b035d8bd93b1a605bac24a3f0dd166e3316' ;;
    node-v24.19.0-linux-arm64.tar.gz) printf '%s' 'd28c8a5bf0a808f0ed434a1dce8c54ae98f0371c0bd86ac58abc613f73e6643f' ;;
    node-v24.19.0-linux-x64.tar.gz) printf '%s' 'f625d97cd707df4ff96254916fbc5ff014f09c09effe5a1e0ca8f6d41a8789d4' ;;
    *) die "no Node.js checksum is configured for $1" ;;
  esac
}

node_is_supported() {
  local executable="$1" version semver major minor
  [[ -x "$executable" ]] || return 1
  version="$($executable --version 2>/dev/null || true)"
  semver="${version#v}"
  IFS=. read -r major minor _ <<<"$semver"
  [[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ ]] || return 1
  if [[ "$major" == '22' && "$minor" -ge 19 ]]; then return 0; fi
  [[ "$major" -ge 24 && "$major" -lt 27 ]]
}

install_managed_node() {
  local runtime_root active_node archive_name archive_url archive_fallback_url archive_sha archive_root
  local archive_file extracted_root destination backup resolved_node

  runtime_root="$MCODE_INSTALL_DIR/runtime"
  active_node="$runtime_root/node"
  if node_is_supported "$active_node/bin/node" && [[ -x "$active_node/bin/npm" ]]; then
    NODE_RUNTIME_KIND='managed'
    NODE_BIN="$active_node/bin"
    resolved_node="$(cd "$active_node" && pwd -P)"
    NODE_EXECUTABLE="$resolved_node/bin/node"
    NPM_EXECUTABLE="$resolved_node/bin/npm"
    log "Using managed Node.js $($NODE_EXECUTABLE --version)"
    return
  fi

  archive_name="node-v${NODE_VERSION}-${TARGET_OS}-${TARGET_ARCH}.tar.gz"
  archive_root="${MCODE_NODE_ARCHIVE_ROOT:-${archive_name%.tar.gz}}"
  archive_url="${MCODE_NODE_ARCHIVE_URL:-${NODE_DIST_BASE%/}/${archive_name}}"
  archive_fallback_url=''
  if [[ -z "${MCODE_NODE_ARCHIVE_URL:-}" && -z "${MCODE_NODE_DIST_BASE:-}" && \
    -n "$NODE_FALLBACK_DIST_BASE" ]]; then
    archive_fallback_url="${NODE_FALLBACK_DIST_BASE%/}/${archive_name}"
  fi
  archive_sha="${MCODE_NODE_ARCHIVE_SHA256:-$(node_archive_sha256 "$archive_name")}"
  archive_file="$TEMP_ROOT/$archive_name"

  have tar || die 'tar is required to install the managed Node.js runtime.'
  log "Downloading managed Node.js v${NODE_VERSION}"
  log 'Downloading and verifying Node.js checksum'
  download_verified "$archive_url" "$archive_file" "$archive_sha" 'Node.js archive' "$archive_fallback_url"

  mkdir -p "$TEMP_ROOT/node-extract" "$runtime_root"
  tar -xzf "$archive_file" -C "$TEMP_ROOT/node-extract"
  extracted_root="$TEMP_ROOT/node-extract/$archive_root"
  [[ -x "$extracted_root/bin/node" && -x "$extracted_root/bin/npm" ]] || \
    die 'downloaded Node.js archive is missing node or npm.'

  destination="$runtime_root/$archive_root"
  if [[ -e "$destination" || -L "$destination" ]]; then
    backup="$destination.incomplete.$(date +%s)"
    mv "$destination" "$backup"
    warn "Moved an incomplete managed Node.js runtime to $backup"
  fi
  mv "$extracted_root" "$destination"
  if [[ -e "$active_node" || -L "$active_node" ]]; then
    if [[ -L "$active_node" ]]; then
      unlink "$active_node"
    else
      backup="$active_node.previous.$(date +%s)"
      mv "$active_node" "$backup"
      warn "Moved the previous managed Node.js runtime to $backup"
    fi
  fi
  ln -s "$destination" "$active_node"

  NODE_BIN="$active_node/bin"
  NODE_EXECUTABLE="$destination/bin/node"
  NPM_EXECUTABLE="$destination/bin/npm"
  NODE_RUNTIME_KIND='managed'
  node_is_supported "$NODE_EXECUTABLE" || die 'managed Node.js failed its version check.'
  log "Installed managed Node.js $($NODE_EXECUTABLE --version)"
}

select_node_runtime() {
  local system_node system_npm system_version npm_version
  if have node && have npm; then
    system_node="$(command -v node)"
    system_npm="$(command -v npm)"
    npm_version="$($system_npm --version 2>/dev/null || true)"
    if node_is_supported "$system_node" && [[ "$npm_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
      NODE_EXECUTABLE="$system_node"
      NPM_EXECUTABLE="$system_npm"
      NODE_BIN="$(dirname "$system_node")"
      NODE_RUNTIME_KIND='system'
      log "Using system Node.js $($NODE_EXECUTABLE --version) and npm $npm_version"
      return
    fi
    system_version="$($system_node --version 2>/dev/null || printf 'unknown')"
    if node_is_supported "$system_node"; then
      warn 'System Node.js is supported but npm is not working; installing an isolated Node.js runtime.'
    else
      warn "System Node.js $system_version is not supported; installing an isolated Node.js ${NODE_VERSION} runtime."
    fi
  elif have node; then
    warn 'System Node.js was found but npm is missing; installing an isolated Node.js runtime.'
  fi
  install_managed_node
}

resolve_release() {
  local metadata
  log "Resolving ${MCODE_PACKAGE_NAME}@latest from ${MCODE_NPM_REGISTRY}"
  metadata="$($NPM_EXECUTABLE view "${MCODE_PACKAGE_NAME}@latest" version --json \
    --registry "$MCODE_NPM_REGISTRY" --fetch-timeout 30000)" || \
    die "could not resolve ${MCODE_PACKAGE_NAME}@latest from ${MCODE_NPM_REGISTRY}"
  MCODE_PACKAGE_VERSION="$($NODE_EXECUTABLE -e '
    const parsed = JSON.parse(process.argv[1]);
    const value = Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : parsed;
    if (typeof value !== "string" || !/^\d+\.\d+\.\d+$/.test(value)) {
      throw new Error("npm latest must resolve to a stable semantic version");
    }
    process.stdout.write(value);
  ' "$metadata")" || die 'npm latest metadata is invalid.'
  log "Resolved ${MCODE_PACKAGE_NAME}@${MCODE_PACKAGE_VERSION}"
}

detect_shell_rc() {
  local shell_name
  shell_name="$(basename "${SHELL:-/bin/bash}")"
  case "$shell_name" in
    zsh) printf '%s' "$HOME/.zshrc" ;;
    bash)
      if [[ -f "$HOME/.bashrc" ]]; then printf '%s' "$HOME/.bashrc"
      elif [[ -f "$HOME/.bash_profile" ]]; then printf '%s' "$HOME/.bash_profile"
      elif [[ -f "$HOME/.profile" ]]; then printf '%s' "$HOME/.profile"
      else printf '%s' "$HOME/.bashrc"
      fi
      ;;
    fish) printf '%s' "$HOME/.config/fish/config.fish" ;;
    *) printf '%s' "$HOME/.profile" ;;
  esac
}

update_path() {
  local install_bin="$MCODE_INSTALL_DIR/bin" rc marker path_line
  export PATH="$NODE_BIN:$install_bin:$PATH"
  if [[ -n "$MCODE_NO_MODIFY_PATH" ]]; then
    log 'Skipping persistent PATH update (MCODE_NO_MODIFY_PATH is set)'
    return
  fi

  rc="$(detect_shell_rc)"
  PATH_UPDATED_FILE="$rc"
  marker='# MiniMax Code CLI'
  mkdir -p "$(dirname "$rc")"
  if grep -Fqs "$install_bin" "$rc" 2>/dev/null; then
    log "PATH is already configured in $rc"
    return
  fi
  if [[ "$rc" == *fish* ]]; then
    path_line="fish_add_path -g \"$install_bin\""
  else
    path_line="export PATH=\"$install_bin:\$PATH\""
  fi
  printf '\n%s\n%s\n' "$marker" "$path_line" >>"$rc"
  log "Added MiniMax Code to PATH in $rc"
}

npm_root_for() {
  "$NPM_EXECUTABLE" root --global --prefix "$1"
}

assert_no_legacy_install() {
  local root
  root="$(npm_root_for "$MCODE_INSTALL_DIR")"
  if [[ -d "$root/@minimax/mcode" ]]; then
    die 'The selected MCODE_INSTALL_DIR contains the legacy CLI. Choose a dedicated MCODE_INSTALL_DIR; the new TUI installer will not modify the legacy CLI.'
  fi
}

remove_staged_conflicting_packages() {
  local root package
  root="$(npm_root_for "$STAGING_PREFIX")"
  for package in '@minimax/code'; do
    if [[ -d "$root/$package" ]]; then
      log "Removing conflicting staged package $package"
      "$NPM_EXECUTABLE" uninstall --global --prefix "$STAGING_PREFIX" "$package" \
        --no-audit --no-fund >/dev/null
    fi
  done
}

read_installed_version() {
  local release='' manifest
  if [[ -f "$MCODE_INSTALL_DIR/current" ]]; then
    release="$(tr -d '\r\n' <"$MCODE_INSTALL_DIR/current")"
  fi
  case "$release" in
    ''|*[!0-9A-Za-z._-]*) manifest="$MCODE_INSTALL_DIR/lib/node_modules/@minimax-ai/code/package.json" ;;
    *) manifest="$MCODE_INSTALL_DIR/releases/$release/lib/node_modules/@minimax-ai/code/package.json" ;;
  esac
  [[ -f "$manifest" ]] || return 1
  "$NODE_EXECUTABLE" -e '
    const fs = require("node:fs");
    const manifest = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
    if (manifest.name !== "@minimax-ai/code" || !/^\d+\.\d+\.\d+$/.test(manifest.version)) process.exit(1);
    process.stdout.write(manifest.version);
  ' "$manifest" 2>/dev/null
}

receipt_is_healthy() {
  local receipt="$MCODE_INSTALL_DIR/install.json"
  [[ -f "$receipt" ]] || return 1
  "$NODE_EXECUTABLE" -e '
    const fs = require("node:fs");
    const path = require("node:path");
    const [file, packageName, prefix] = process.argv.slice(1);
    const value = JSON.parse(fs.readFileSync(file, "utf8"));
    const allowedRegistries = new Set([
      "https://registry.npmjs.org/",
      "https://registry.npmmirror.com/",
    ]);
    const valid = value.schemaVersion === 2 && value.layoutVersion === 2 &&
      value.releasesDirectory === "releases" && value.currentFile === "current" &&
      value.product === "minimax-code" &&
      value.updateOwner === "npm-prefix" && value.packageManager === "npm" &&
      value.packageName === packageName && value.distTag === "latest" &&
      typeof value.registry === "string" && allowedRegistries.has(new URL(value.registry).href) &&
      typeof value.prefix === "string" && path.resolve(value.prefix) === path.resolve(prefix) &&
      typeof value.npmExecutable === "string" && fs.existsSync(value.npmExecutable);
    process.exit(valid ? 0 : 1);
  ' "$receipt" "$MCODE_PACKAGE_NAME" "$MCODE_INSTALL_DIR" 2>/dev/null
}

legacy_receipt_is_healthy() {
  local receipt="$MCODE_INSTALL_DIR/install.json"
  [[ -f "$receipt" ]] || return 1
  "$NODE_EXECUTABLE" -e '
    const fs = require("node:fs");
    const path = require("node:path");
    const [file, packageName, prefix] = process.argv.slice(1);
    const value = JSON.parse(fs.readFileSync(file, "utf8"));
    const valid = value.schemaVersion === 1 && value.product === "minimax-code" &&
      value.updateOwner === "npm-prefix" && value.packageManager === "npm" &&
      value.packageName === packageName && value.distTag === "latest" &&
      typeof value.prefix === "string" && path.resolve(value.prefix) === path.resolve(prefix) &&
      typeof value.npmExecutable === "string" && fs.existsSync(value.npmExecutable);
    process.exit(valid ? 0 : 1);
  ' "$receipt" "$MCODE_PACKAGE_NAME" "$MCODE_INSTALL_DIR" 2>/dev/null
}

verify_native_sqlite() {
  local prefix="$1" node="$2"
  "$node" --input-type=module --eval '
    import { createRequire } from "node:module";
    const require = createRequire(process.argv[1]);
    const Database = require("better-sqlite3");
    const database = new Database(":memory:");
    try {
      if (database.prepare("SELECT 1 AS value").get()?.value !== 1) process.exitCode = 1;
    } finally { database.close(); }
  ' "$prefix/lib/node_modules/@minimax-ai/code/package.json"
}

read_installed_node() {
  "$NODE_EXECUTABLE" -e '
    const fs = require("node:fs");
    const path = require("node:path");
    const node = JSON.parse(fs.readFileSync(process.argv[1], "utf8")).nodeExecutable;
    if (typeof node !== "string" || !path.isAbsolute(node) || !fs.existsSync(node)) process.exit(1);
    process.stdout.write(node);
  ' "$MCODE_INSTALL_DIR/install.json" 2>/dev/null
}

legacy_installation_is_healthy() {
  local launcher="$MCODE_INSTALL_DIR/bin/mcode" native_binding actual_version installed_node
  native_binding="$MCODE_INSTALL_DIR/lib/node_modules/@minimax-ai/code/node_modules/better-sqlite3/build/Release/better_sqlite3.node"
  INSTALLED_VERSION="$(read_installed_version 2>/dev/null || true)"
  [[ -n "$INSTALLED_VERSION" && -x "$launcher" && -f "$native_binding" ]] || return 1
  actual_version="$("$NODE_EXECUTABLE" "$launcher" --version 2>/dev/null || true)"
  [[ "$actual_version" == "$INSTALLED_VERSION" ]] || return 1
  legacy_receipt_is_healthy || return 1
  installed_node="$(read_installed_node)" || return 1
  verify_native_sqlite "$MCODE_INSTALL_DIR" "$installed_node" >/dev/null 2>&1
}

installation_is_healthy() {
  local mcode_bin="$MCODE_INSTALL_DIR/bin/mcode" mcode_tools_bin="$MCODE_INSTALL_DIR/bin/mcode-tools"
  local actual_version mcode_tools_version release='' native_binding installed_node
  INSTALLED_VERSION="$(read_installed_version 2>/dev/null || true)"
  if [[ -f "$MCODE_INSTALL_DIR/current" ]]; then
    release="$(tr -d '\r\n' <"$MCODE_INSTALL_DIR/current")"
  fi
  case "$release" in ''|*[!0-9A-Za-z._-]*) return 1 ;; esac
  native_binding="$MCODE_INSTALL_DIR/releases/$release/lib/node_modules/@minimax-ai/code/node_modules/better-sqlite3/build/Release/better_sqlite3.node"
  [[ -n "$INSTALLED_VERSION" && -x "$mcode_bin" && -x "$mcode_tools_bin" && -f "$native_binding" ]] || return 1
  actual_version="$("$mcode_bin" --version 2>/dev/null || true)"
  [[ "$actual_version" == "$INSTALLED_VERSION" ]] || return 1
  mcode_tools_version="$("$mcode_tools_bin" --version 2>/dev/null || true)"
  [[ -n "$mcode_tools_version" ]] || return 1
  receipt_is_healthy || return 1
  # Older receipts do not identify the launcher's Node; repair once rather than
  # declaring an ABI-mismatched installation healthy using a different runtime.
  installed_node="$(read_installed_node)" || return 1
  verify_native_sqlite "$MCODE_INSTALL_DIR/releases/$release" "$installed_node" >/dev/null 2>&1
}

compare_versions() {
  "$NODE_EXECUTABLE" -e '
    const left = process.argv[1].split(".").map(Number);
    const right = process.argv[2].split(".").map(Number);
    for (let index = 0; index < 3; index += 1) {
      if (left[index] !== right[index]) {
        process.stdout.write(left[index] < right[index] ? "-1" : "1");
        process.exit(0);
      }
    }
    process.stdout.write("0");
  ' "$1" "$2"
}

classify_install_action() {
  local comparison current_manifest="$MCODE_INSTALL_DIR/lib/node_modules/@minimax-ai/code/package.json"
  local current_launcher="$MCODE_INSTALL_DIR/bin/mcode" current_receipt="$MCODE_INSTALL_DIR/install.json"
  if installation_is_healthy; then
    comparison="$(compare_versions "$INSTALLED_VERSION" "$MCODE_PACKAGE_VERSION")"
    if [[ "$comparison" == '0' ]]; then INSTALL_ACTION='current'
    elif [[ "$comparison" == '1' ]]; then INSTALL_ACTION='ahead'
    else INSTALL_ACTION='update'
    fi
    return
  fi
  if legacy_installation_is_healthy; then
    comparison="$(compare_versions "$INSTALLED_VERSION" "$MCODE_PACKAGE_VERSION")"
    if [[ "$comparison" == '1' ]]; then INSTALL_ACTION='ahead'
    elif [[ "$comparison" == '-1' ]]; then INSTALL_ACTION='update'
    else INSTALL_ACTION='repair'
    fi
    return
  fi

  INSTALLED_VERSION="$(read_installed_version 2>/dev/null || true)"
  if [[ -n "$INSTALLED_VERSION" && "$(compare_versions "$INSTALLED_VERSION" "$MCODE_PACKAGE_VERSION")" == '1' ]]; then
    die "Installed MCode $INSTALLED_VERSION could not be verified; refusing to repair it with older version $MCODE_PACKAGE_VERSION."
  fi
  if [[ -e "$current_manifest" || -e "$current_launcher" || -e "$current_receipt" ]]; then
    INSTALL_ACTION='repair'
  else
    INSTALL_ACTION='fresh'
  fi
}

recover_interrupted_install() {
  local modules_root="$MCODE_INSTALL_DIR/lib/node_modules"
  local modules_backup="${modules_root}.mcode-install-backup"
  local launcher="$MCODE_INSTALL_DIR/bin/mcode"
  local launcher_backup="${launcher}.mcode-install-backup"
  local receipt="$MCODE_INSTALL_DIR/install.json"
  local receipt_backup="${receipt}.mcode-install-backup"
  local journal="$MCODE_INSTALL_DIR/.mcode-install-transaction"

  if [[ ! -e "$modules_backup" && ! -e "$launcher_backup" && ! -e "$receipt_backup" ]]; then
    rm -f -- "$journal"
    return
  fi

  warn 'Recovering the previous MCode installation from an interrupted transaction.'
  if [[ -e "$modules_backup" ]]; then
    rm -rf -- "$modules_root"
    mv "$modules_backup" "$modules_root"
  fi
  if [[ -e "$launcher_backup" ]]; then
    rm -f -- "$launcher"
    mv "$launcher_backup" "$launcher"
  fi
  if [[ -e "$receipt_backup" ]]; then
    rm -f -- "$receipt"
    mv "$receipt_backup" "$receipt"
  fi
  rm -f -- "$journal"
}

prepare_staging_prefix() {
  mkdir -p "$MCODE_INSTALL_DIR/releases"
  STAGING_PREFIX="$(mktemp -d "$MCODE_INSTALL_DIR/releases/.staging-${MCODE_PACKAGE_VERSION}.XXXXXX")"
}

npm_install_exact_version() {
  local prefix="$1"
  if [[ -n "$MCODE_BETTER_SQLITE3_BINARY_HOST_MIRROR" ]]; then
    export npm_config_better_sqlite3_binary_host_mirror="$MCODE_BETTER_SQLITE3_BINARY_HOST_MIRROR"
  fi
  "$NPM_EXECUTABLE" install --global --prefix "$prefix" \
    "${MCODE_PACKAGE_NAME}@${MCODE_PACKAGE_VERSION}" \
    --registry "$MCODE_NPM_REGISTRY" \
    --foreground-scripts \
    --ignore-scripts=false \
    --include=optional \
    --allow-scripts="${MCODE_PACKAGE_NAME},better-sqlite3" \
    --no-audit --no-fund
}

install_staged_package() {
  local npm_log="$TEMP_ROOT/npm-install.log"
  remove_staged_conflicting_packages
  log "Installing ${MCODE_PACKAGE_NAME}@${MCODE_PACKAGE_VERSION} into staging"
  if ! npm_install_exact_version "$STAGING_PREFIX" 2>&1 | tee "$npm_log"; then
    if [[ -n "$NATIVE_BUILD_TOOLS_MISSING" ]] && \
      grep -Eiq 'gyp ERR!|node-gyp|not found: (make|g\+\+|c\+\+)|Could not find any Python' "$npm_log"; then
      print_native_build_tools_help
      die 'npm could not build the native SQLite module because the Linux build toolchain is incomplete.'
    fi
    warn 'The first npm installation attempt failed. Retrying npm installation once.'
    if ! npm_install_exact_version "$STAGING_PREFIX" 2>&1 | tee "$npm_log"; then
      print_native_build_tools_help
      die 'npm installation failed twice. Check network access to npm/GitHub; if native prebuilds are unavailable, install platform build tools and rerun this same command.'
    fi
  fi
}

verify_prefix_installation() {
  local prefix="$1" mcode_bin="$1/bin/mcode" mcode_tools_bin="$1/bin/mcode-tools"
  local actual_version mcode_tools_version
  [[ -x "$mcode_bin" && -x "$mcode_tools_bin" ]] || return 1
  actual_version="$("$NODE_EXECUTABLE" "$mcode_bin" --version 2>/dev/null || true)"
  [[ "$actual_version" == "$MCODE_PACKAGE_VERSION" ]] || return 1
  mcode_tools_version="$("$NODE_EXECUTABLE" "$mcode_tools_bin" --version 2>/dev/null || true)"
  [[ -n "$mcode_tools_version" ]] || return 1
  verify_native_sqlite "$prefix" "$NODE_EXECUTABLE"
}

verify_versioned_release() {
  local prefix="$1" actual_version mcode_tools_version
  verify_prefix_installation "$prefix" || return 1
  [[ -x "$prefix/.mcode-launcher" && -x "$prefix/.mcode-tools-launcher" ]] || return 1
  actual_version="$("$prefix/.mcode-launcher" --version 2>/dev/null || true)"
  [[ "$actual_version" == "$MCODE_PACKAGE_VERSION" ]] || return 1
  mcode_tools_version="$("$prefix/.mcode-tools-launcher" --version 2>/dev/null || true)"
  [[ -n "$mcode_tools_version" ]]
}

activate_staged_install() {
  local release_key="$MCODE_PACKAGE_VERSION" release_prefix="$MCODE_INSTALL_DIR/releases/$MCODE_PACKAGE_VERSION"
  write_versioned_launchers "$STAGING_PREFIX" || return 1
  verify_versioned_release "$STAGING_PREFIX" || return 1
  if [[ -e "$release_prefix" ]]; then
    release_key="${MCODE_PACKAGE_VERSION}-repair-$$-${RANDOM:-0}"
    release_prefix="$MCODE_INSTALL_DIR/releases/$release_key"
    [[ ! -e "$release_prefix" ]] || return 1
  fi
  mv "$STAGING_PREFIX" "$release_prefix" || return 1
  STAGING_PREFIX=''
  verify_versioned_release "$release_prefix" || return 1
  write_install_receipt || return 1
  if ! stable_launchers_are_valid; then
    install_stable_launchers || return 1
  fi
  printf '%s\n' "$release_key" >"$MCODE_INSTALL_DIR/current.tmp-$$"
  mv -f "$MCODE_INSTALL_DIR/current.tmp-$$" "$MCODE_INSTALL_DIR/current"
}

write_versioned_launchers() {
  local release_prefix="$1"
  "$NODE_EXECUTABLE" -e '
    const fs = require("node:fs");
    const path = require("node:path");
    const [prefix, node, packageName] = process.argv.slice(1);
    const manifestFile = path.join(prefix, "lib", "node_modules", ...packageName.split("/"), "package.json");
    const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
    const commands = [
      ["mcode", manifest.bin?.mcode, ".mcode-launcher"],
      ["mcode-tools", manifest.bin?.["mcode-tools"], ".mcode-tools-launcher"],
    ];
    if (!fs.existsSync(node)) process.exit(1);
    const quote = (value) => `\x27${value.replaceAll("\x27", `\x27"\x27"\x27`)}\x27`;
    for (const [, bin, launcher] of commands) {
      if (typeof bin !== "string" || path.isAbsolute(bin) || bin.split(/[\\/]/).includes("..")) process.exit(1);
      const entry = path.join(path.dirname(manifestFile), ...bin.replaceAll("\\", "/").split("/"));
      const relativeEntry = path.relative(prefix, entry).replaceAll(path.sep, "/");
      if (!fs.existsSync(entry) || !relativeEntry || relativeEntry.startsWith("../") || path.isAbsolute(relativeEntry)) process.exit(1);
      const relative = relativeEntry.replaceAll("\\", "/").replaceAll("\"", "\\\"").replaceAll("$", "\\$").replaceAll("`", "\\`");
      fs.writeFileSync(path.join(prefix, launcher), `#!/bin/sh\nset -eu\nroot=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)\nexec ${quote(node)} "$root/${relative}" "$@"\n`, { mode: 0o700 });
    }
  ' "$release_prefix" "$NODE_EXECUTABLE" "$MCODE_PACKAGE_NAME"
}

stable_launchers_are_valid() {
  grep -Fqs 'releases/$release/.mcode-launcher' "$MCODE_INSTALL_DIR/bin/mcode" 2>/dev/null &&
    grep -Fqs 'releases/$release/.mcode-tools-launcher' "$MCODE_INSTALL_DIR/bin/mcode-tools" 2>/dev/null
}

install_stable_launchers() {
  local launcher="$MCODE_INSTALL_DIR/bin/mcode" temporary="${MCODE_INSTALL_DIR}/bin/.mcode.tmp-$$"
  local tools_launcher="$MCODE_INSTALL_DIR/bin/mcode-tools"
  local tools_temporary="${MCODE_INSTALL_DIR}/bin/.mcode-tools.tmp-$$"
  mkdir -p "$MCODE_INSTALL_DIR/bin"
  cat >"$temporary" <<'EOF'
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)
release=$(tr -d '\r\n' < "$root/current")
case "$release" in ""|*[!0-9A-Za-z._-]*) echo "Invalid MCode current release pointer." >&2; exit 1;; esac
exec "$root/releases/$release/.mcode-launcher" "$@"
EOF
  cat >"$tools_temporary" <<'EOF'
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)
release=$(tr -d '\r\n' < "$root/current")
case "$release" in ""|*[!0-9A-Za-z._-]*) echo "Invalid MCode current release pointer." >&2; exit 1;; esac
exec "$root/releases/$release/.mcode-tools-launcher" "$@"
EOF
  chmod 700 "$temporary" "$tools_temporary"
  mv -f "$temporary" "$launcher"
  mv -f "$tools_temporary" "$tools_launcher"
}

print_run_help() {
  local mcode_bin="$MCODE_INSTALL_DIR/bin/mcode"
  printf '\nRun now:\n  %s\n\n' "$mcode_bin"
  if [[ -n "$PATH_UPDATED_FILE" ]]; then
    printf 'Use in this terminal now:\n  source "%s"\n  mcode\n\n' "$PATH_UPDATED_FILE"
    printf 'Or open a new terminal, then run:\n  mcode\n\n'
  else
    printf 'Run:\n  mcode\n\n'
  fi
}

install_mcode() {
  classify_install_action
  case "$INSTALL_ACTION" in
    current)
      log "MCode ${INSTALLED_VERSION} is already the latest version; skipping installation."
      update_path
      print_run_help
      return
      ;;
    ahead)
      warn "Installed MCode ${INSTALLED_VERSION} is newer than latest ${MCODE_PACKAGE_VERSION}; leaving it unchanged."
      update_path
      print_run_help
      return
      ;;
    update) log "Updating MCode ${INSTALLED_VERSION} to ${MCODE_PACKAGE_VERSION}" ;;
    repair) warn 'Migrating or repairing the existing MCode installation with versioned releases.' ;;
    fresh) log "Installing MCode ${MCODE_PACKAGE_VERSION}" ;;
  esac

  prepare_staging_prefix
  install_staged_package
  verify_prefix_installation "$STAGING_PREFIX" || \
    die 'staged MCode verification failed; the existing installation was left unchanged.'

  stage 5 'Activating the verified installation'
  activate_staged_install || \
    die 'versioned activation failed; the previous MCode installation remains available.'
  update_path
  log "Installation complete: ${MCODE_PACKAGE_NAME}@${MCODE_PACKAGE_VERSION} (${INSTALL_ACTION})"
  print_run_help
}

write_install_receipt() {
  local npm_executable receipt_file
  npm_executable="$(cd "$(dirname "$NPM_EXECUTABLE")" && pwd -P)/$(basename "$NPM_EXECUTABLE")"
  receipt_file="$MCODE_INSTALL_DIR/install.json"
  "$NODE_EXECUTABLE" -e '
    const fs = require("node:fs");
    const [file, packageName, registry, prefix, npmExecutable] = process.argv.slice(1);
    const receipt = {
      schemaVersion: 2,
      product: "minimax-code",
      updateOwner: "npm-prefix",
      packageManager: "npm",
      packageName,
      registry: new URL(registry).href,
      distTag: "latest",
      npmExecutable,
      nodeExecutable: process.execPath,
      prefix,
      layoutVersion: 2,
      releasesDirectory: "releases",
      currentFile: "current",
    };
    const temporary = `${file}.tmp-${process.pid}`;
    fs.writeFileSync(temporary, `${JSON.stringify(receipt, null, 2)}\n`, { mode: 0o600 });
    fs.renameSync(temporary, file);
  ' "$receipt_file" "$MCODE_PACKAGE_NAME" "$MCODE_NPM_REGISTRY" \
    "$MCODE_INSTALL_DIR" "$npm_executable"
}

main() {
  show_intro
  stage 1 'Checking platform and install location'
  detect_target
  mkdir -p "$MCODE_INSTALL_DIR"
  acquire_install_lock
  recover_interrupted_install
  assert_no_legacy_update_transaction
  TEMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/minimax-code-install.XXXXXX")"
  stage 2 'Preparing a compatible Node.js runtime'
  select_node_runtime
  # Pin launchers and receipts to the same executable, not a version-manager symlink.
  NODE_EXECUTABLE="$("$NODE_EXECUTABLE" -p 'process.execPath')"
  NODE_BIN="$(dirname "$NODE_EXECUTABLE")"
  # npm uses /usr/bin/env node; lifecycle scripts must use the selected runtime too.
  export PATH="$NODE_BIN:$PATH"
  detect_native_build_tools
  assert_no_legacy_install
  stage 3 'Resolving the stable MCode release'
  resolve_release
  stage 4 'Checking whether to install, update, repair, or skip'
  install_mcode
}

main
