#!/usr/bin/env sh
# EdgeSpeak CLI 安装脚本（自包含 / standalone）
#   curl -fsSL https://edgespeak.com/install.sh | sh
#
# 面向开发者 / headless / CI：装上即用，**无需安装 EdgeSpeak 桌面 App**。
# 拉取自包含运行时（瘦 CLI + edgespeak-mcp 引擎宿主 + 本地转录引擎 + native dylib），
# 解压到 runtime 目录后只把 `edgespeak-cli` 软链进 PATH。普通用户仍可装桌面 App
# （官网 DMG），两条路并存、各服务各自人群。
#
# 支持 macOS Apple Silicon 和 Linux x86_64。Linux 优先探测 NVIDIA GPU 的 compute
# capability，选择对应的精简 CUDA 包；探测失败或不在支持范围时安全回退 CPU 包。
# 原生 Windows 不走本脚本，未来提供单独的 PowerShell 脚本 (install.ps1)。
#
# 装完首次使用需在本机激活一次。推荐账号浏览器登录（新账号自动获得 7 天试用，
# 已购账号直接激活）；也可本设备匿名试用或用授权 Key 激活：
#   edgespeak-cli login
#   edgespeak-cli trial
#   edgespeak-cli activate <KEY>
#
# 面向全球：消息默认英文，检测到系统语言为中文 (LC_ALL/LC_MESSAGES/LANG = zh*) 时切中文。
set -eu

# 下载源根 (cli 根，平台目录在其下)。EDGESPEAK_DOWNLOAD_BASE 可覆盖 (测试 / staging / 镜像)。
DOWNLOAD_BASE="${EDGESPEAK_DOWNLOAD_BASE:-https://download.edgespeak.com/cli}"
# 发布渠道：默认 stable（正式发布产物）；预发布必须显式 opt-in，安装器绝不自动升级渠道。
# 设置 EDGESPEAK_CLI_PREVIEW_TAG=cli-preview-vX 时改从 preview 路径安装该 tag 的候选
# 产物（发布前验证用，与 CI consumer 同一布局与校验）。EDGESPEAK_CLI_PREVIEW_BASE 可
# 覆盖 preview 源根（与 CI 脚本同名环境变量）。
PREVIEW_BASE="${EDGESPEAK_CLI_PREVIEW_BASE:-https://download.edgespeak.com/preview/cli}"
PREVIEW_TAG="${EDGESPEAK_CLI_PREVIEW_TAG:-}"
# 运行时目录：放自包含二进制 + dylib（可整目录原子替换；与用户数据 / vault / 模型分离，
# 后者位于平台用户数据目录，不在此目录）。
RUNTIME_DIR="${EDGESPEAK_RUNTIME_DIR:-$HOME/.edgespeak/runtime}"
INSTALL_DIR="${EDGESPEAK_INSTALL_DIR:-$HOME/.local/bin}"
BIN_NAME="edgespeak-cli"
# 旧版桌面 App 曾把 CLI 链到这里。新版本与 standalone 共用 ~/.local/bin；
# 仅确认目标属于 EdgeSpeak.app 时才自动迁移，未知文件绝不删除。
LEGACY_INSTALL_PATH="${EDGESPEAK_LEGACY_INSTALL_PATH:-/usr/local/bin/edgespeak-cli}"
OSASCRIPT_BIN="${EDGESPEAK_OSASCRIPT_BIN:-/usr/bin/osascript}"
CODESIGN_BIN="${EDGESPEAK_CODESIGN_BIN:-/usr/bin/codesign}"

# 语言检测：仅区分「中文」与「其它 (默认英文)」。
case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in
  zh* | *.zh | *zh_* | *zh-*) ES_LANG="zh" ;;
  *)                          ES_LANG="en" ;;
esac
# 双语取词：$1=英文 (默认), $2=中文。
say() { if [ "$ES_LANG" = "zh" ]; then printf '%s' "$2"; else printf '%s' "$1"; fi; }

err()  { printf '\033[31m%s\033[0m\n' "$*" >&2; }
warn() { printf '\033[33m%s\033[0m\n' "$*" >&2; }
info() { printf '\033[32m%s\033[0m\n' "$*"; }

# preview tag 校验与 CI producer/consumer 保持同一正则（平台解耦命名
# cli-preview-macos-v* / cli-preview-linux-v*，legacy cli-preview-v* 兼容）。
if [ -n "$PREVIEW_TAG" ] \
  && ! printf '%s' "$PREVIEW_TAG" | grep -Eq '^cli-preview(-macos|-linux)?-v[0-9A-Za-z][0-9A-Za-z._-]*$'; then
  err "$(say "Invalid EDGESPEAK_CLI_PREVIEW_TAG: ${PREVIEW_TAG}." "EDGESPEAK_CLI_PREVIEW_TAG 无效: ${PREVIEW_TAG}。")"
  exit 1
fi

cli_target_is_trusted_app() {
  case "$1" in
    /*/EdgeSpeak.app/Contents/Resources/edgespeak-cli) ;;
    *) return 1 ;;
  esac
  legacy_app_path="${1%/Contents/Resources/edgespeak-cli}"
  [ -f "$1" ] || return 1
  [ -f "${legacy_app_path}/Contents/Info.plist" ] || return 1
  [ -x "$CODESIGN_BIN" ] || return 1
  "$CODESIGN_BIN" --verify --strict "$legacy_app_path" >/dev/null 2>&1 || return 1
  signing_info="$("$CODESIGN_BIN" -dvv "$legacy_app_path" 2>&1)" || return 1
  printf '%s\n' "$signing_info" | grep -F 'TeamIdentifier=75N5GA66XV' >/dev/null 2>&1 \
    || return 1
  bundle_id="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' \
    "${legacy_app_path}/Contents/Info.plist" 2>/dev/null || true)"
  case "$bundle_id" in
    com.edgespeak.app | com.edgespeak.desktop) return 0 ;;
    *) return 1 ;;
  esac
}

validate_cli_entrypoints() {
  # RUNTIME_DIR 已存在、非空、且没有 EdgeSpeak runtime 标志物（Resources/edgespeak-cli）时
  # 直接拒绝：后续原子替换会整目录 mv + 删除，一旦调用方（如旧版 edgespeak-cli update
  # 未解析软链取祖父目录）把 ~/.local 之类用户目录误传进来，等于清空用户数据。
  # 宁可安装失败，绝不删除非本安装器管理的内容。
  if [ -e "$RUNTIME_DIR" ] && [ ! -e "${RUNTIME_DIR}/Resources/${BIN_NAME}" ] \
    && [ -n "$(ls -A "$RUNTIME_DIR" 2>/dev/null)" ]; then
    err "$(say "Refusing to replace ${RUNTIME_DIR}: it exists but is not an EdgeSpeak runtime directory. Unset EDGESPEAK_RUNTIME_DIR or point it at an empty directory." "拒绝替换 ${RUNTIME_DIR}: 目录已存在但不是 EdgeSpeak runtime 目录。请取消 EDGESPEAK_RUNTIME_DIR 或指向空目录。")"
    exit 1
  fi

  CANONICAL_LINK="${INSTALL_DIR}/${BIN_NAME}"
  CANONICAL_PREVIOUS_PRESENT=0
  CANONICAL_PREVIOUS_TARGET=""
  if [ -e "$CANONICAL_LINK" ] || [ -L "$CANONICAL_LINK" ]; then
    canonical_target="$(readlink "$CANONICAL_LINK" 2>/dev/null || true)"
    if [ -z "$canonical_target" ]; then
      err "$(say "Cannot install: ${CANONICAL_LINK} exists and is not an EdgeSpeak-managed symlink." "无法安装: ${CANONICAL_LINK} 已存在且不是 EdgeSpeak 管理的软链。")"
      exit 1
    fi
    case "$canonical_target" in
      */EdgeSpeak.app/Contents/Resources/edgespeak-cli)
        if ! cli_target_is_trusted_app "$canonical_target"; then
          err "$(say "Cannot install: ${CANONICAL_LINK} points to an untrusted or missing EdgeSpeak App." "无法安装: ${CANONICAL_LINK} 指向不受信任或已不存在的 EdgeSpeak App。")"
          exit 1
        fi
        ;;
      *)
        if [ "$canonical_target" != "${RUNTIME_DIR}/Resources/${BIN_NAME}" ]; then
          err "$(say "Cannot install: ${CANONICAL_LINK} points to an unknown target: ${canonical_target}" "无法安装: ${CANONICAL_LINK} 指向未知目标: ${canonical_target}")"
          exit 1
        fi
        ;;
    esac
    CANONICAL_PREVIOUS_PRESENT=1
    CANONICAL_PREVIOUS_TARGET="$canonical_target"
  fi

  [ "$OS" = Darwin ] || return 0
  if [ -e "$LEGACY_INSTALL_PATH" ] || [ -L "$LEGACY_INSTALL_PATH" ]; then
    legacy_target="$(readlink "$LEGACY_INSTALL_PATH" 2>/dev/null || true)"
    if [ -z "$legacy_target" ] || ! cli_target_is_trusted_app "$legacy_target"; then
      err "$(say "Cannot migrate ${LEGACY_INSTALL_PATH}: it is not an EdgeSpeak App symlink. Remove or rename it manually." "无法迁移 ${LEGACY_INSTALL_PATH}: 它不是 EdgeSpeak App 软链, 请手动移除或改名。")"
      exit 1
    fi
  fi
}

install_canonical_cli_link() {
  mkdir -p "$INSTALL_DIR"
  canonical_temporary="${INSTALL_DIR}/.${BIN_NAME}.tmp.$$"
  rm -f "$canonical_temporary"
  if ! ln -s "${RUNTIME_DIR}/Resources/${BIN_NAME}" "$canonical_temporary"; then
    err "$(say "Could not create the new CLI entrypoint." "无法创建新的 CLI 入口。")"
    return 1
  fi
  if ! mv -f "$canonical_temporary" "$CANONICAL_LINK"; then
    rm -f "$canonical_temporary"
    err "$(say "Could not activate the new CLI entrypoint." "无法启用新的 CLI 入口。")"
    return 1
  fi
}

rollback_canonical_cli_link() {
  attempted_target="${RUNTIME_DIR}/Resources/${BIN_NAME}"
  current_target="$(readlink "$CANONICAL_LINK" 2>/dev/null || true)"
  if [ "$current_target" != "$attempted_target" ]; then
    err "$(say "The CLI entrypoint changed during migration; refusing to overwrite it." "迁移期间 CLI 入口被外部修改，拒绝覆盖。")"
    return 1
  fi
  if [ "$CANONICAL_PREVIOUS_PRESENT" = "1" ]; then
    rollback_temporary="${INSTALL_DIR}/.${BIN_NAME}.rollback.$$"
    rm -f "$rollback_temporary"
    if ! ln -s "$CANONICAL_PREVIOUS_TARGET" "$rollback_temporary"; then
      return 1
    fi
    if ! mv -f "$rollback_temporary" "$CANONICAL_LINK"; then
      rm -f "$rollback_temporary"
      return 1
    fi
  else
    rm -f "$CANONICAL_LINK"
  fi
}

migrate_legacy_cli_link() {
  [ "$OS" = Darwin ] || return 0
  [ -e "$LEGACY_INSTALL_PATH" ] || [ -L "$LEGACY_INSTALL_PATH" ] || return 0

  legacy_target="$(readlink "$LEGACY_INSTALL_PATH" 2>/dev/null || true)"
  if [ -z "$legacy_target" ] || ! cli_target_is_trusted_app "$legacy_target"; then
    err "$(say "Refusing to remove an unmanaged legacy CLI entrypoint." "拒绝删除不受 EdgeSpeak 管理的旧 CLI 入口。")"
    return 1
  fi
  info "$(say "Migrating the legacy CLI entrypoint to ${INSTALL_DIR}/${BIN_NAME}…" "正在把旧 CLI 入口迁移到 ${INSTALL_DIR}/${BIN_NAME}…")"
  if ! "$OSASCRIPT_BIN" \
    -e 'on run argv' \
    -e 'set legacyPath to item 1 of argv' \
    -e 'set expectedTarget to item 2 of argv' \
    -e 'set shellCommand to "/usr/bin/test \"$(/usr/bin/readlink " & quoted form of legacyPath & ")\" = " & quoted form of expectedTarget & " && /bin/rm -f " & quoted form of legacyPath' \
    -e 'do shell script shellCommand with administrator privileges' \
    -e 'end run' \
    "$LEGACY_INSTALL_PATH" "$legacy_target"; then
    err "$(say "Could not remove the legacy CLI entrypoint; installation stopped." "无法移除旧 CLI 入口, 安装已停止。")"
    return 1
  fi
  if [ -e "$LEGACY_INSTALL_PATH" ] || [ -L "$LEGACY_INSTALL_PATH" ]; then
    err "$(say "The legacy CLI entrypoint still exists; installation stopped." "旧 CLI 入口仍然存在, 安装已停止。")"
    return 1
  fi
  info "$(say "Removed legacy entrypoint ${LEGACY_INSTALL_PATH}" "已移除旧入口 ${LEGACY_INSTALL_PATH}")"
}

# 支持矩阵：把 (uname -s)/(uname -m) 映射到「平台目录 + tarball 文件名」。
# 扩平台的唯一改动点——在此加 case 分支即可。
PLATFORM_DIR=""
ASSET=""
LINUX_PROFILE=""

profile_for_compute_capability() {
  case "$1" in
    5.0 | 5.2 | 5.3 | 6.0 | 6.1 | 7.0 | 7.2 | 7.5) printf '%s' cuda-legacy ;;
    8.0 | 8.6 | 8.7 | 8.9 | 9.0) printf '%s' cuda-modern ;;
    # 11.0 当前是 Jetson (ARM64)，不属于本脚本的 Linux x86_64 资产矩阵。
    10.0 | 10.3 | 12.0 | 12.1) printf '%s' cuda-blackwell ;;
    *) return 1 ;;
  esac
}

linux_cuda_runtime_available() {
  command -v ldconfig >/dev/null 2>&1 || return 1
  libraries="$(ldconfig -p 2>/dev/null)" || return 1
  for library in libcuda.so.1 libcudart.so.12 libcublas.so.12 libcublasLt.so.12; do
    printf '%s\n' "$libraries" | grep -F "$library" >/dev/null 2>&1 || return 1
  done
}

select_linux_profile() {
  requested="${EDGESPEAK_LINUX_PROFILE:-auto}"
  case "$requested" in
    cpu | cuda-legacy | cuda-modern | cuda-blackwell)
      LINUX_PROFILE="$requested"
      return
      ;;
    auto) ;;
    *)
      err "$(say "Invalid EDGESPEAK_LINUX_PROFILE: ${requested}." "EDGESPEAK_LINUX_PROFILE 无效: ${requested}。")"
      exit 1
      ;;
  esac

  LINUX_PROFILE="cpu"
  command -v nvidia-smi >/dev/null 2>&1 || return
  capabilities="$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader,nounits 2>/dev/null)" \
    || return
  [ -n "$capabilities" ] || return

  detected_profile=""
  while IFS= read -r capability; do
    capability="$(printf '%s' "$capability" | tr -d '[:space:]')"
    profile="$(profile_for_compute_capability "$capability")" || return
    if [ -n "$detected_profile" ] && [ "$detected_profile" != "$profile" ]; then
      # 异构多卡可能被 CUDA_DEVICE_ORDER / CUDA_VISIBLE_DEVICES 重新排序，保守回退 CPU。
      return
    fi
    detected_profile="$profile"
  done <<EOF
$capabilities
EOF
  if [ -n "$detected_profile" ] && linux_cuda_runtime_available; then
    LINUX_PROFILE="$detected_profile"
  fi
}

resolve_target() {
  case "$1/$2" in
    Darwin/arm64)  PLATFORM_DIR="macos-arm64";  ASSET="edgespeak-cli-standalone-macos-arm64.tar.gz" ;;
    Linux/x86_64)
      PLATFORM_DIR="linux-x86_64"
      select_linux_profile
      case "$LINUX_PROFILE" in
        cpu) ASSET="edgespeak-cli-mcp-linux-x86_64-cpu.tar.gz" ;;
        cuda-legacy) ASSET="edgespeak-cli-mcp-linux-x86_64-cuda-legacy.tar.gz" ;;
        cuda-modern) ASSET="edgespeak-cli-mcp-linux-x86_64-cuda-modern.tar.gz" ;;
        cuda-blackwell) ASSET="edgespeak-cli-mcp-linux-x86_64-cuda-blackwell.tar.gz" ;;
      esac
      ;;
    # 规划中 (资产就绪后取消注释即上线)：
    # Darwin/x86_64) PLATFORM_DIR="macos-x86_64"; ASSET="edgespeak-cli-standalone-macos-x86_64.tar.gz" ;;
    # Linux/aarch64) PLATFORM_DIR="linux-arm64";  ASSET="edgespeak-cli-standalone-linux-arm64.tar.gz" ;;
    *) return 1 ;;
  esac
}

# sha256 计算：macOS 用 shasum，Linux 用 sha256sum——自动择一，为未来 Linux 就绪。
sha256_of() {
  if command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$1" | awk '{print $1}'
  elif command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | awk '{print $1}'
  else
    err "$(say "shasum or sha256sum is required but neither was found." "需要 shasum 或 sha256sum, 均未找到。")"
    exit 1
  fi
}

# 1) 平台门槛
OS="$(uname -s)"
ARCH="$(uname -m)"
# Rosetta 2 下的终端在 Apple Silicon 上 uname -m 会回 x86_64；探测真实硬件避免误判不支持。
if [ "$OS" = "Darwin" ] && [ "$ARCH" = "x86_64" ]; then
  if [ "$(sysctl -in sysctl.proc_translated 2>/dev/null)" = "1" ] || [ "$(sysctl -n hw.optional.arm64 2>/dev/null)" = "1" ]; then
    ARCH="arm64"
  fi
fi
if ! resolve_target "$OS" "$ARCH"; then
  err "$(say "EdgeSpeak CLI does not support this platform yet: ${OS} ${ARCH}." "EdgeSpeak CLI 暂不支持当前平台: ${OS} ${ARCH}。")"
  case "$OS" in
    Linux)                err "$(say "Linux currently requires x86_64." "Linux 当前要求 x86_64。")" ;;
    MINGW* | MSYS* | CYGWIN*) err "$(say "A Windows build is coming soon, with a PowerShell installer." "Windows 版即将推出, 届时请用 PowerShell 安装脚本。")" ;;
  esac
  err "$(say "Supported platforms: macOS Apple Silicon and Linux x86_64." "支持的平台: macOS Apple Silicon 和 Linux x86_64。")"
  exit 1
fi

command -v curl >/dev/null 2>&1 || { err "$(say "curl is required but was not found." "需要 curl, 未找到。")"; exit 1; }
command -v tar  >/dev/null 2>&1 || { err "$(say "tar is required but was not found." "需要 tar, 未找到。")"; exit 1; }

# 资产目录：stable 按平台目录分层；preview 与 CI consumer 同布局
# （macOS 资产在 tag 根，Linux 资产在 tag/linux/ 下）。
if [ -n "$PREVIEW_TAG" ]; then
  CHANNEL="preview"
  if [ "$OS" = "Linux" ]; then
    ASSET_BASE="${PREVIEW_BASE}/${PREVIEW_TAG}/linux"
  else
    ASSET_BASE="${PREVIEW_BASE}/${PREVIEW_TAG}"
  fi
else
  CHANNEL="stable"
  ASSET_BASE="${DOWNLOAD_BASE}/${PLATFORM_DIR}"
fi
URL="${ASSET_BASE}/${ASSET}"

if [ "${EDGESPEAK_INSTALL_DRY_RUN:-0}" = "1" ]; then
  printf 'os=%s\narch=%s\nplatform=%s\nprofile=%s\nchannel=%s\npreviewTag=%s\nasset=%s\nurl=%s\n' \
    "$OS" "$ARCH" "$PLATFORM_DIR" "${LINUX_PROFILE:-metal}" "$CHANNEL" "$PREVIEW_TAG" "$ASSET" "$URL"
  exit 0
fi

if [ "$CHANNEL" = "preview" ]; then
  warn "$(say "Installing a PREVIEW build (${PREVIEW_TAG}) — a release candidate for testing, not for production use." "正在安装 PREVIEW 构建 (${PREVIEW_TAG}) — 发布前候选版本, 仅用于验证, 不建议生产使用。")"
fi

validate_cli_entrypoints

# 2) 下载 tarball (含 sha256) 到临时目录
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
info "$(say "Downloading edgespeak-cli (${PLATFORM_DIR}${LINUX_PROFILE:+, ${LINUX_PROFILE}}, self-contained)…" "下载 edgespeak-cli (${PLATFORM_DIR}${LINUX_PROFILE:+, ${LINUX_PROFILE}}, 自包含)…")"
if ! curl -fsSL "$URL" -o "${TMP}/${ASSET}"; then
  parts_file="${TMP}/${ASSET}.parts"
  curl -fsSL "${URL}.parts" -o "$parts_file" || {
    err "$(say "Could not download the runtime or its parts manifest." "无法下载 runtime 或其分片清单。")"
    exit 1
  }
  : > "${TMP}/${ASSET}"
  while IFS='|' read -r part_name part_size part_sha; do
    case "$part_name" in
      "${ASSET}.part-"[0-9][0-9][0-9]) ;;
      *) err "$(say "Invalid artifact part name: ${part_name}." "产物分片名称无效: ${part_name}。")"; exit 1 ;;
    esac
    case "$part_size" in '' | *[!0-9]*) err "$(say "Invalid artifact part size." "产物分片大小无效。")"; exit 1 ;; esac
    [ "$part_size" -gt 0 ] 2>/dev/null \
      || { err "$(say "Invalid artifact part size." "产物分片大小无效。")"; exit 1; }
    printf '%s\n' "$part_sha" | grep -Eq '^[0-9a-f]{64}$' \
      || { err "$(say "Invalid artifact part checksum." "产物分片校验值无效。")"; exit 1; }
    curl -fsSL "${ASSET_BASE}/${part_name}" -o "${TMP}/${part_name}"
    [ "$(wc -c < "${TMP}/${part_name}" | tr -d '[:space:]')" = "$part_size" ] || {
      err "$(say "Artifact part size mismatch: ${part_name}." "产物分片大小不匹配: ${part_name}。")"
      exit 1
    }
    [ "$(sha256_of "${TMP}/${part_name}")" = "$part_sha" ] || {
      err "$(say "Artifact part checksum mismatch: ${part_name}." "产物分片校验失败: ${part_name}。")"
      exit 1
    }
    cat "${TMP}/${part_name}" >> "${TMP}/${ASSET}"
  done < "$parts_file"
fi
curl -fsSL "${URL}.sha256" -o "${TMP}/${ASSET}.sha256" || true

# 3) 完整性校验 (拿不到 checksum 直接中止——别装来路不明的二进制)
if [ ! -s "${TMP}/${ASSET}.sha256" ]; then
  err "$(say "Could not fetch the sha256 checksum; aborting." "未取到 sha256 校验文件, 安装中止。")"
  exit 1
fi
EXPECT="$(awk '{print $1}' "${TMP}/${ASSET}.sha256")"
ACTUAL="$(sha256_of "${TMP}/${ASSET}")"
if [ "$EXPECT" != "$ACTUAL" ]; then
  err "$(say "Checksum verification failed: sha256 mismatch; aborting." "校验失败: sha256 不匹配, 安装中止。")"
  err "$(say "  expected ${EXPECT}" "  期望 ${EXPECT}")"
  err "$(say "  actual   ${ACTUAL}" "  实际 ${ACTUAL}")"
  exit 1
fi
info "$(say "Integrity verified (${ACTUAL})" "完整性校验通过 (${ACTUAL})")"

# 4) 安全校验布局 + 验签 + 同分区原子替换 runtime 目录
#    tarball 顶层结构（镜像 App bundle Contents/，满足 mcp / serve 的相对 rpath）：
#      Resources/edgespeak-cli, Resources/edgespeak-mcp,
#      Resources/edgespeak-engine/{edgespeak-serve, liblattifai_engine.dylib}
#    （单 dylib 已含 llama/ggml/mtmd 与 omni_* VAD/AED，无独立 Frameworks/）

# 4a) RUNTIME_DIR 边界防护：空 / 根 / 家目录绝不 rm -rf（防 env 误配把家目录删了）。
case "$RUNTIME_DIR" in
  "" | "/" | "$HOME" | "$HOME/")
    err "$(say "Refusing to use unsafe runtime dir: '${RUNTIME_DIR}'" "拒绝使用危险的 runtime 目录: '${RUNTIME_DIR}'")"
    exit 1
    ;;
esac

# 4b) 包内条目白名单：只允许 Resources/，拒绝绝对路径 / .. / 异常条目。
#     （sha256 只防传输损坏；这里防恶意/畸形包把文件写到目录外。）
ENTRIES="$(tar -tzf "${TMP}/${ASSET}")"
if printf '%s\n' "$ENTRIES" | grep -vE '^$' | grep -qvE '^Resources/'; then
  err "$(say "Archive has unexpected entries; aborting." "压缩包含非预期条目, 安装中止。")"
  exit 1
fi
if printf '%s\n' "$ENTRIES" | grep -qE '(^/|(^|/)\.\.(/|$))'; then
  err "$(say "Archive has absolute or .. paths; aborting." "压缩包含绝对路径或 .. , 安装中止。")"
  exit 1
fi

# 4c) 解压到与 RUNTIME_DIR 同父目录的临时 staging（保证随后 mv 同分区、原子）。
PARENT="$(dirname "$RUNTIME_DIR")"
mkdir -p "$PARENT"
LOCK_DIR="${RUNTIME_DIR}.install-lock"
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
  err "$(say "Another EdgeSpeak CLI installation is already running." "另一个 EdgeSpeak CLI 安装进程正在运行。")"
  exit 1
fi
STAGE_NEW="${PARENT}/.edgespeak-runtime.new.$$"
rm -rf "$STAGE_NEW"
mkdir -p "$STAGE_NEW"
trap 'rm -rf "$TMP" "$STAGE_NEW" "$LOCK_DIR"' EXIT
tar -xzf "${TMP}/${ASSET}" -C "$STAGE_NEW"

CLI_PATH="${STAGE_NEW}/Resources/${BIN_NAME}"
MCP_PATH="${STAGE_NEW}/Resources/edgespeak-mcp"
require_file() {
  if [ ! -f "$1" ]; then
    err "$(say "Unexpected archive layout: missing $(basename "$1"); aborting." "压缩包结构异常: 缺 $(basename "$1"), 安装中止。")"
    rm -rf "$STAGE_NEW"
    exit 1
  fi
}
require_file "$CLI_PATH"
require_file "$MCP_PATH"
if [ "$OS" = Darwin ]; then
  SERVE_PATH="${STAGE_NEW}/Resources/edgespeak-engine/edgespeak-serve"
  require_file "$SERVE_PATH"
  require_file "${STAGE_NEW}/Resources/edgespeak-engine/liblattifai_engine.dylib"
else
  runtime_profile="cpu"
  [ "$LINUX_PROFILE" = cpu ] || runtime_profile="cuda"
  SERVE_PATH="${STAGE_NEW}/Resources/edgespeak-serve-${runtime_profile}"
  NATIVE_PATH="${STAGE_NEW}/Resources/edgespeak-native-${runtime_profile}"
  RUNTIME_MANIFEST_PATH="${STAGE_NEW}/Resources/runtime-manifest.json"
  require_file "$SERVE_PATH"
  require_file "$NATIVE_PATH"
  require_file "$RUNTIME_MANIFEST_PATH"
  if [ "$runtime_profile" = cuda ]; then
    require_file "${STAGE_NEW}/Resources/edgespeak-native-cpu"
  fi
fi

# 4d) macOS 验签：sha256 同源不防 bucket/CDN/DNS 被替换。要求二进制由 EdgeSpeak 的
#     Developer ID（Team 75N5GA66XV）签名才放行（ad-hoc / 来路不明包会被拒）。macOS 自带 codesign。
EXPECT_TEAM="75N5GA66XV"
if [ "$OS" = Darwin ] && [ -x "$CODESIGN_BIN" ]; then
  for f in "$CLI_PATH" "$MCP_PATH" "$SERVE_PATH"; do
    if ! "$CODESIGN_BIN" --verify --strict "$f" >/dev/null 2>&1; then
      err "$(say "Signature verification failed for $(basename "$f"); aborting." "签名校验失败: $(basename "$f"), 安装中止。")"
      rm -rf "$STAGE_NEW"
      exit 1
    fi
    team="$("$CODESIGN_BIN" -dvv "$f" 2>&1 | sed -n 's/^TeamIdentifier=//p')"
    if [ "$team" != "$EXPECT_TEAM" ]; then
      err "$(say "Unexpected code signer for $(basename "$f") (team='${team}'); aborting." "$(basename "$f") 签名者异常 (team='${team}'), 安装中止。")"
      rm -rf "$STAGE_NEW"
      exit 1
    fi
  done
  info "$(say "Code signature verified (Team ${EXPECT_TEAM})" "代码签名校验通过 (Team ${EXPECT_TEAM})")"
elif [ "$OS" = Darwin ]; then
  err "$(say "codesign not found; cannot verify the runtime signature. Aborting." "未找到 codesign, 无法校验运行时签名, 安装中止。")"
  rm -rf "$STAGE_NEW"
  exit 1
fi

if [ "$OS" = Linux ]; then
  expected_runtime_profile="cpu"
  [ "$LINUX_PROFILE" = cpu ] || expected_runtime_profile="cuda"
  grep -q "\"profile\": \"${expected_runtime_profile}\"" "$RUNTIME_MANIFEST_PATH" \
    || { err "$(say "Linux runtime profile mismatch; aborting." "Linux runtime profile 不匹配, 安装中止。")"; rm -rf "$STAGE_NEW"; exit 1; }
  grep -q "\"distributionProfile\": \"${LINUX_PROFILE}\"" "$RUNTIME_MANIFEST_PATH" \
    || { err "$(say "Linux distribution profile mismatch; aborting." "Linux 分发 profile 不匹配, 安装中止。")"; rm -rf "$STAGE_NEW"; exit 1; }

  # 在替换现有 runtime 之前验证动态依赖和 sidecar 协议。检测偶有误差时也不会
  # 把一套无法启动的 CUDA runtime 安装到用户机器上。
  chmod +x "$CLI_PATH" "$MCP_PATH" "$SERVE_PATH" "$NATIVE_PATH"
  if [ "$runtime_profile" = cuda ]; then
    chmod +x "${STAGE_NEW}/Resources/edgespeak-native-cpu"
  fi
  for binary in "$SERVE_PATH" "$NATIVE_PATH"; do
    missing_libraries="$(ldd "$binary" 2>/dev/null | sed -n 's/^[[:space:]]*\([^[:space:]]*\)[[:space:]]*=>[[:space:]]*not found.*$/\1/p')"
    if [ -n "$missing_libraries" ]; then
      err "$(say "Missing Linux runtime libraries for $(basename "$binary"): ${missing_libraries}." "$(basename "$binary") 缺少 Linux 运行库: ${missing_libraries}。")"
      rm -rf "$STAGE_NEW"
      exit 1
    fi
    if ! "$binary" --handshake >/dev/null 2>&1; then
      err "$(say "Runtime handshake failed for $(basename "$binary"); aborting." "$(basename "$binary") runtime 握手失败, 安装中止。")"
      rm -rf "$STAGE_NEW"
      exit 1
    fi
  done
  if [ "$runtime_profile" = cuda ] \
    && ! "${STAGE_NEW}/Resources/edgespeak-native-cpu" --handshake >/dev/null 2>&1; then
    err "$(say "Runtime handshake failed for edgespeak-native-cpu; aborting." "edgespeak-native-cpu runtime 握手失败, 安装中止。")"
    rm -rf "$STAGE_NEW"
    exit 1
  fi

  if ! "$CLI_PATH" --version >/dev/null 2>&1; then
    err "$(say "edgespeak-cli preflight failed; aborting." "edgespeak-cli 预检失败, 安装中止。")"
    rm -rf "$STAGE_NEW"
    exit 1
  fi
  command -v timeout >/dev/null 2>&1 || {
    err "$(say "GNU timeout is required for the MCP preflight." "MCP 预检需要 GNU timeout。")"
    rm -rf "$STAGE_NEW"
    exit 1
  }
  MCP_SMOKE_DIR="${TMP}/mcp-smoke"
  mkdir -p "$MCP_SMOKE_DIR"
  if ! printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"installer","version":"1"}}}' \
    | EDGESPEAK_DATA_DIR="$MCP_SMOKE_DIR" timeout 10 "$MCP_PATH" mcp > "${TMP}/mcp-smoke.json" 2>/dev/null \
    || ! grep -q '"id":1.*"result"' "${TMP}/mcp-smoke.json"; then
    err "$(say "edgespeak-mcp preflight failed; aborting." "edgespeak-mcp 预检失败, 安装中止。")"
    rm -rf "$STAGE_NEW"
    exit 1
  fi
fi

# 4e) 同分区原子替换：旧 runtime 先挪到 .old，新 runtime mv 就位，成功后清旧。
chmod +x "$CLI_PATH" "$MCP_PATH" "$SERVE_PATH" ${NATIVE_PATH:+"$NATIVE_PATH"} 2>/dev/null || true
RUNTIME_OLD="${PARENT}/.edgespeak-runtime.old.$$"
rm -rf "$RUNTIME_OLD"
[ -e "$RUNTIME_DIR" ] && mv "$RUNTIME_DIR" "$RUNTIME_OLD"
if ! mv "$STAGE_NEW" "$RUNTIME_DIR"; then
  [ -e "$RUNTIME_OLD" ] && mv "$RUNTIME_OLD" "$RUNTIME_DIR"
  err "$(say "Could not replace the runtime; the previous installation was restored." "无法替换 runtime, 已恢复原安装。")"
  exit 1
fi
rm -rf "$RUNTIME_OLD"
rm -rf "$LOCK_DIR"
info "$(say "Installed runtime to ${RUNTIME_DIR}" "运行时已安装到 ${RUNTIME_DIR}")"

# 5) 先原子切换用户级入口，再迁移旧入口；若授权取消或目标在授权期间发生变化，
#    恢复切换前的用户级入口，避免留下两个版本由 PATH 顺序决定。
install_canonical_cli_link || exit 1
if ! migrate_legacy_cli_link; then
  if rollback_canonical_cli_link; then
    err "$(say "The previous CLI entrypoint was restored." "已恢复迁移前的 CLI 入口。")"
  else
    err "$(say "Could not restore the previous CLI entrypoint; inspect ${CANONICAL_LINK} manually." "无法恢复迁移前的 CLI 入口, 请手动检查 ${CANONICAL_LINK}。")"
  fi
  exit 1
fi
info "$(say "Linked ${INSTALL_DIR}/${BIN_NAME}" "已软链 ${INSTALL_DIR}/${BIN_NAME}")"

# 6) 确保在 PATH 上 (不在则写进对应 shell 的 rc)
case ":${PATH}:" in
  *":${INSTALL_DIR}:"*) ;;
  *)
    SHELL_NAME="$(basename "${SHELL:-/bin/sh}")"
    case "$SHELL_NAME" in
      zsh)  RC="${HOME}/.zshrc" ;;
      # macOS 交互式 bash 读 .bash_profile，Linux 读 .bashrc——按平台择一。
      bash) [ "$OS" = "Linux" ] && RC="${HOME}/.bashrc" || RC="${HOME}/.bash_profile" ;;
      *)    RC="${HOME}/.profile" ;;
    esac
    if [ -f "$RC" ] && grep -qF "$INSTALL_DIR" "$RC" 2>/dev/null; then
      :
    else
      printf '\n# EdgeSpeak CLI\nexport PATH="%s:$PATH"\n' "$INSTALL_DIR" >> "$RC"
      info "$(say "Added ${INSTALL_DIR} to PATH (in ${RC})" "已把 ${INSTALL_DIR} 加入 PATH (写到 ${RC})")"
    fi
    info "$(say "Restart your terminal, or run: source ${RC}" "请重开终端, 或运行: source ${RC}")"
    ;;
esac

# 7) 撞名检测：已知的桌面 App 旧入口会在上一步迁移；其它 PATH 撞名不属于
#    EdgeSpeak 管理范围，只提示并保留用户文件。
FOUND="$(command -v "$BIN_NAME" 2>/dev/null || true)"
if [ -n "$FOUND" ] && [ "$FOUND" != "${INSTALL_DIR}/${BIN_NAME}" ]; then
  warn "$(say "⚠ Another ${BIN_NAME} on PATH may shadow this install: ${FOUND}" "⚠ PATH 上已有另一个 ${BIN_NAME}, 可能遮蔽本次安装: ${FOUND}")"
  if [ -L "$FOUND" ]; then
    warn "$(say "  It is a symlink to: $(readlink "$FOUND")" "  它是软链, 指向: $(readlink "$FOUND")")"
  fi
  warn "$(say "  After removing it, restart your terminal (or run: hash -r)." "  移除后重开终端 (或运行: hash -r)。")"
fi

# 8) 验证 + 下一步引导
VER="$("${RUNTIME_DIR}/Resources/${BIN_NAME}" --version 2>/dev/null || true)"
info ""
info "$(say "✓ Installation complete${VER:+: ${VER}}" "✓ 安装完成${VER:+: ${VER}}")"
# 共享 vault 检测：桌面 App 与 CLI 共用同一 license 凭据（credentials.enc）。文件已
# 存在 = 本机激活过（正式或试用），首次激活引导没有意义。路径解析与 edgespeak-mcp
# 的跨平台 data dir 规则一致：EDGESPEAK_DATA_DIR 覆盖 > 平台默认。
if [ -n "${EDGESPEAK_DATA_DIR:-}" ]; then
  VAULT_FILE="${EDGESPEAK_DATA_DIR}/credentials.enc"
elif [ "$OS" = Darwin ]; then
  VAULT_FILE="${HOME}/Library/Application Support/EdgeSpeak/credentials.enc"
elif [ -n "${XDG_DATA_HOME:-}" ]; then
  VAULT_FILE="${XDG_DATA_HOME}/EdgeSpeak/credentials.enc"
else
  VAULT_FILE="${HOME}/.local/share/EdgeSpeak/credentials.enc"
fi
# 更新场景（edgespeak-cli update 传 EDGESPEAK_INSTALL_CONTEXT=update）：本机已激活，
# 不再重复首次激活引导，避免把已登录用户引去开匿名 trial。
if [ "${EDGESPEAK_INSTALL_CONTEXT:-}" = "update" ]; then
  :
elif [ -f "$VAULT_FILE" ]; then
  info "$(say "Existing EdgeSpeak activation found on this machine — the CLI is ready to use." "检测到本机已有 EdgeSpeak 激活信息, CLI 可直接使用。")"
  info "$(say "Try: edgespeak-cli transcribe <audio-file>" "试试: edgespeak-cli transcribe <音频文件>")"
else
  info "$(say "Next: activate this machine (one-time)." "下一步: 激活本机 (一次性)。")"
  info "$(say "Recommended — sign in with your account from the browser; new accounts get a 7-day free trial, purchased accounts activate directly:" "推荐: 用你的账号浏览器登录, 新账号自动获得 7 天免费试用, 已购账号直接激活:")"
  info "    edgespeak-cli login"
  info "$(say "Quick anonymous trial on this device (no account): edgespeak-cli trial" "本设备匿名快速试用 (无需账号): edgespeak-cli trial")"
  info "$(say "Already have a license key: edgespeak-cli activate <KEY>  (or --stdin to keep it out of shell history)" "已有授权 Key: edgespeak-cli activate <KEY>  (或 --stdin, Key 不进 shell 历史)")"
  info "$(say "Headless / CI: pass the key via the EDGESPEAK_LICENSE_KEY env var." "Headless / CI: 用环境变量 EDGESPEAK_LICENSE_KEY 传 Key。")"
  info "$(say "Purchase: https://edgespeak.com" "购买: https://edgespeak.com")"
  info "$(say "Then: edgespeak-cli transcribe <audio-file>" "然后: edgespeak-cli transcribe <音频文件>")"
fi
info "$(say "Check status anytime: edgespeak-cli status" "随时查看状态: edgespeak-cli status")"
