#!/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 (arm64) 有产物。脚本按「多平台」结构编写：
# 新增 macOS x86_64 / Linux 等只需在 resolve_target() 的支持矩阵里加一行，
# 并确保 R2 上有对应 tarball——下游下载 / 校验 / 解压 / 软链逻辑全平台通用。
# 原生 Windows 不走本脚本，未来提供单独的 PowerShell 脚本 (install.ps1)。
#
# 装完首次使用需在本机激活一次。推荐浏览器登录（新账号自动获得 7 天试用，
# 已购账号直接激活）；已有授权 Key 也可直接激活：
#   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}"
# 运行时目录：放自包含二进制 + dylib（可整目录原子替换；与用户数据 / vault / 模型分离，
# 后者由 App 与 CLI 共用 ~/Library/Application Support/EdgeSpeak，不在此目录）。
RUNTIME_DIR="${EDGESPEAK_RUNTIME_DIR:-$HOME/.edgespeak/runtime}"
INSTALL_DIR="${EDGESPEAK_INSTALL_DIR:-$HOME/.local/bin}"
BIN_NAME="edgespeak-cli"

# 语言检测：仅区分「中文」与「其它 (默认英文)」。
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' "$*"; }

# 支持矩阵：把 (uname -s)/(uname -m) 映射到「平台目录 + tarball 文件名」。
# 扩平台的唯一改动点——在此加 case 分支即可。
PLATFORM_DIR=""
ASSET=""
resolve_target() {
  case "$1/$2" in
    Darwin/arm64)  PLATFORM_DIR="macos-arm64";  ASSET="edgespeak-cli-standalone-macos-arm64.tar.gz" ;;
    # 规划中 (资产就绪后取消注释即上线)：
    # Darwin/x86_64) PLATFORM_DIR="macos-x86_64"; ASSET="edgespeak-cli-standalone-macos-x86_64.tar.gz" ;;
    # Linux/x86_64)  PLATFORM_DIR="linux-x86_64"; ASSET="edgespeak-cli-standalone-linux-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 "A Linux build is coming soon." "Linux 版即将推出 (coming soon)。")" ;;
    MINGW* | MSYS* | CYGWIN*) err "$(say "A Windows build is coming soon, with a PowerShell installer." "Windows 版即将推出, 届时请用 PowerShell 安装脚本。")" ;;
  esac
  err "$(say "Only macOS Apple Silicon (arm64) is supported right now." "目前仅支持 macOS Apple Silicon (arm64)。")"
  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; }

URL="${DOWNLOAD_BASE}/${PLATFORM_DIR}/${ASSET}"

# 2) 下载 tarball (含 sha256) 到临时目录
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
info "$(say "Downloading edgespeak-cli (${PLATFORM_DIR}, self-contained)…" "下载 edgespeak-cli (${PLATFORM_DIR}, 自包含)…")"
curl -fsSL "$URL"          -o "${TMP}/${ASSET}"
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"
STAGE_NEW="${PARENT}/.edgespeak-runtime.new.$$"
rm -rf "$STAGE_NEW"
mkdir -p "$STAGE_NEW"
tar -xzf "${TMP}/${ASSET}" -C "$STAGE_NEW"

CLI_PATH="${STAGE_NEW}/Resources/${BIN_NAME}"
MCP_PATH="${STAGE_NEW}/Resources/edgespeak-mcp"
SERVE_PATH="${STAGE_NEW}/Resources/edgespeak-engine/edgespeak-serve"
ENGINE_DYLIB_PATH="${STAGE_NEW}/Resources/edgespeak-engine/liblattifai_engine.dylib"
for f in "$CLI_PATH" "$MCP_PATH" "$SERVE_PATH" "$ENGINE_DYLIB_PATH"; do
  if [ ! -f "$f" ]; then
    err "$(say "Unexpected archive layout: missing $(basename "$f"); aborting." "压缩包结构异常: 缺 $(basename "$f"), 安装中止。")"
    rm -rf "$STAGE_NEW"
    exit 1
  fi
done

# 4d) 验签：sha256 同源不防 bucket/CDN/DNS 被替换。要求二进制由 EdgeSpeak 的
#     Developer ID（Team 75N5GA66XV）签名才放行（ad-hoc / 来路不明包会被拒）。macOS 自带 codesign。
EXPECT_TEAM="75N5GA66XV"
if command -v codesign >/dev/null 2>&1; then
  for f in "$CLI_PATH" "$MCP_PATH" "$SERVE_PATH"; do
    if ! codesign --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 -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})")"
else
  err "$(say "codesign not found; cannot verify the runtime signature. Aborting." "未找到 codesign, 无法校验运行时签名, 安装中止。")"
  rm -rf "$STAGE_NEW"
  exit 1
fi

# 4e) 同分区原子替换：旧 runtime 先挪到 .old，新 runtime mv 就位，成功后清旧。
chmod +x "$CLI_PATH" "$MCP_PATH" "$SERVE_PATH" 2>/dev/null || true
RUNTIME_OLD="${PARENT}/.edgespeak-runtime.old.$$"
rm -rf "$RUNTIME_OLD"
[ -e "$RUNTIME_DIR" ] && mv "$RUNTIME_DIR" "$RUNTIME_OLD"
mv "$STAGE_NEW" "$RUNTIME_DIR"
rm -rf "$RUNTIME_OLD"
info "$(say "Installed runtime to ${RUNTIME_DIR}" "运行时已安装到 ${RUNTIME_DIR}")"

# 5) 软链 edgespeak-cli 进 PATH（只软链 cli；它按 symlink 目标同目录解析 edgespeak-mcp）
mkdir -p "$INSTALL_DIR"
ln -sf "${RUNTIME_DIR}/Resources/${BIN_NAME}" "${INSTALL_DIR}/${BIN_NAME}"
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) 撞名检测：PATH 上若有另一个 edgespeak-cli，会遮蔽本次安装。最常见的是桌面 App
#    「安装 CLI 到 PATH」留在 /usr/local/bin 的 root 软链——删除 App 不会清理它。
#    悬空软链会被 PATH 搜索跳过 (command -v 找不到)，故只提示真实存在的可执行撞名。
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
  if [ "$FOUND" = "/usr/local/bin/${BIN_NAME}" ]; then
    warn "$(say "  Likely left by the EdgeSpeak desktop app (Install CLI to PATH); deleting the app does not remove it. Remove it with:" "  多半是桌面 App「安装 CLI 到 PATH」的残留 (删除 App 不会清理它)。移除:")"
    warn "      sudo rm /usr/local/bin/${BIN_NAME}"
  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}}")"
info "$(say "Next: activate this machine (one-time)." "下一步: 激活本机 (一次性)。")"
info "$(say "Recommended — sign in from your browser; new accounts get a 7-day free trial, purchased accounts activate directly:" "推荐: 浏览器登录, 新账号自动获得 7 天免费试用, 已购账号直接激活:")"
info "    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 <音频文件>")"
info "$(say "Check status anytime: edgespeak-cli status" "随时查看状态: edgespeak-cli status")"
