#!/bin/bash
#
# Claude Code 一鍵設定（macOS）
# 說明：https://oawu.tw/blog/macos/claude-code/
#
# 用法：
#   curl -fsSL -o /tmp/claude-code-install.sh https://oawu.tw/blog/macos/claude-code/files/install.sh
#   bash /tmp/claude-code-install.sh
#
# ── 會做的事 ─────────────────────────────────────
#   1. 安裝 Claude Code：下載並執行官方安裝腳本 https://claude.ai/install.sh，
#      裝在 ~/.local/bin/claude，不需要 Node.js。
#      已經有 claude 指令就略過，不論當初是用哪種方式裝的（npm、Homebrew 都算）
#   2. ~/.local/bin/claude 存在時，在 ~/.zshrc 結尾加一行把它加進 PATH（已有則略過）
#   3. 下載 statusline.sh 到 ~/.claude/statusline.sh
#   4. 把 theme、tui、statusLine 三項合併進 ~/.claude/settings.json，
#      其他既有設定保留不動；這三項若原本有值，以本腳本為準
#   5. 驗證：claude --version，並用假資料執行一次 statusline.sh
#
#   檔案內容不同時，舊檔先改名成 *.bak.<時間> 再覆蓋，不會直接蓋掉。
#
# ── 需要人工的步驟 ───────────────────────────────
#   登入帳號：腳本完成後執行 claude，會開啟瀏覽器登入。不影響本腳本的結束代碼。
#   ~/.claude/settings.json 不是合法的 JSON 時無法合併，會以結束代碼 2 結束，
#   修正該檔後再執行一次本腳本。
#
# ── 結束代碼 ─────────────────────────────────────
#   0  完成，驗證全數通過
#   1  失敗（下載失敗、安裝失敗、驗證未通過等），訊息會說明原因
#   2  需要人工步驟，完成後重新執行本腳本
#
# ── 可重複執行 ───────────────────────────────────
#   已完成的步驟會略過，重跑是安全的。
#
# ── 其他 ─────────────────────────────────────────
#   statusline.sh 從 BASE_URL 下載。測試時可用環境變數指到別處，例如本機的開發伺服器：
#     BASE_URL=http://localhost:4321/blog/macos/claude-code/files bash install.sh
#   Claude Code 本身一律從官方網址安裝，不受 BASE_URL 影響。
#

set -euo pipefail

BASE_URL="${BASE_URL:-https://oawu.tw/blog/macos/claude-code/files}"

# 官方安裝腳本。只用官方文件列出的這個網址，不要換成其他來源
OFFICIAL_INSTALLER="https://claude.ai/install.sh"

CLAUDE_DIR="$HOME/.claude"
NATIVE_BIN="$HOME/.local/bin/claude"
ZSHRC="$HOME/.zshrc"
PATH_LINE='export PATH="$HOME/.local/bin:$PATH"'

TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT

# 沒有終端機（例如由其他程式代為執行）時不上色，輸出才不會夾雜控制碼
if [ -t 1 ]; then
  BOLD=$'\033[1m' BLUE=$'\033[1;34m' GREEN=$'\033[32m' GRAY=$'\033[90m' YELLOW=$'\033[33m' RED=$'\033[31m' RESET=$'\033[0m'
else
  BOLD='' BLUE='' GREEN='' GRAY='' YELLOW='' RED='' RESET=''
fi
step() { printf '\n%s==>%s %s%s%s\n' "$BLUE" "$RESET" "$BOLD" "$1" "$RESET"; }
ok()   { printf '    %s✔%s %s\n' "$GREEN" "$RESET" "$1"; }
skip() { printf '    %s- %s%s\n' "$GRAY" "$1" "$RESET"; }
warn() { printf '    %s!%s %s\n' "$YELLOW" "$RESET" "$1"; }
fail() { printf '\n%s✘ %s%s\n' "$RED" "$1" "$RESET" >&2; exit 1; }

# 需要人工處理的步驟：說明要做什麼後以結束代碼 2 結束，做完再重跑。
# 不用 read 等待按鍵，因為由其他程式代為執行時沒有終端機可讀
manual() {
  printf '\n%s需要人工處理：%s\n' "$YELLOW" "$RESET"
  printf '    %s\n' "$@"
  printf '\n完成後重新執行本腳本，已完成的步驟會自動略過。\n'
  exit 2
}

# 從 BASE_URL 下載檔案，輸出暫存檔路徑
fetch() {
  local name="$1" out="$TMP_DIR/$1"
  curl -fsSL "$BASE_URL/$(printf '%s' "$name" | sed 's/ /%20/g')" -o "$out" || fail "下載失敗：$BASE_URL/$name"
  printf '%s' "$out"
}

# 放置檔案：內容相同就略過，不同則先備份再覆蓋
place() {
  local src="$1" dest="$2" label
  label="$(basename "$dest")"
  if [ -f "$dest" ] && cmp -s "$src" "$dest"; then
    skip "$label 已是最新"
    return
  fi
  if [ -f "$dest" ]; then
    local bak="$dest.bak.$(date +%Y%m%d%H%M%S)"
    mv "$dest" "$bak"
    warn "已備份舊檔：$(basename "$bak")"
  fi
  cp "$src" "$dest"
  ok "$label"
}

[ "$(uname)" = "Darwin" ] || fail "此腳本只支援 macOS"

# 1. 安裝 Claude Code
step "安裝 Claude Code"
if command -v claude >/dev/null 2>&1; then
  skip "已安裝：$(command -v claude)"
elif [ -x "$NATIVE_BIN" ]; then
  skip "已安裝：${NATIVE_BIN}（尚未加入 PATH，下一步處理）"
else
  curl -fsSL "$OFFICIAL_INSTALLER" -o "$TMP_DIR/official-install.sh" || fail "下載官方安裝腳本失敗：$OFFICIAL_INSTALLER"
  # stdin 導向 /dev/null：避免以 curl | bash 執行本腳本時，安裝程式把其餘腳本當輸入讀走
  bash "$TMP_DIR/official-install.sh" </dev/null || fail "官方安裝腳本執行失敗，請看上方的訊息"
  [ -x "$NATIVE_BIN" ] || fail "安裝完成但找不到 $NATIVE_BIN"
  ok "Claude Code"
fi

# 2. PATH（只有官方安裝腳本裝的版本需要；npm、Homebrew 版本身就在 PATH 裡）
step "設定 PATH"
if [ ! -x "$NATIVE_BIN" ]; then
  skip "不是用官方安裝腳本裝的，不需要"
elif grep -qsxF "$PATH_LINE" "$ZSHRC"; then
  skip "~/.zshrc 已包含 ~/.local/bin"
else
  printf '\n# Claude Code\n%s\n' "$PATH_LINE" >> "$ZSHRC"
  ok "~/.zshrc 加入 ~/.local/bin"
fi

# 3. 狀態列腳本
step "設定 statusline.sh"
mkdir -p "$CLAUDE_DIR"
f="$(fetch "statusline.sh")"
place "$f" "$CLAUDE_DIR/statusline.sh"

# 4. settings.json：合併三項，保留其他既有設定
step "設定 settings.json"
settings="$CLAUDE_DIR/settings.json"
if ! osascript -l JavaScript - "$settings" "$TMP_DIR/settings.json" <<'JS' >/dev/null 2>"$TMP_DIR/merge.err"
ObjC.import('Foundation')
function run(argv) {
  var src = argv[0], out = argv[1], obj = {}
  if ($.NSFileManager.defaultManager.fileExistsAtPath(src)) {
    var text = $.NSString.stringWithContentsOfFileEncodingError(src, $.NSUTF8StringEncoding, null).js
    if (text.trim() !== '') obj = JSON.parse(text)
  }
  obj.theme = 'dark'
  obj.tui = 'fullscreen'
  obj.statusLine = { type: 'command', command: 'bash ~/.claude/statusline.sh' }
  $(JSON.stringify(obj, null, 2) + '\n').writeToFileAtomicallyEncodingError(out, true, $.NSUTF8StringEncoding, null)
}
JS
then
  manual "~/.claude/settings.json 不是合法的 JSON，無法合併。" \
         "請修正該檔（例如移除註解或多餘的逗號）。"
fi
place "$TMP_DIR/settings.json" "$settings"

# 5. 驗證
step "驗證"
failed=0
claude_bin="$(command -v claude 2>/dev/null || printf '%s' "$NATIVE_BIN")"
if version="$("$claude_bin" --version </dev/null 2>/dev/null)" && [ -n "$version" ]; then
  ok "claude --version：$version"
else
  warn "claude --version 沒有回應"
  failed=$((failed + 1))
fi
sample='{"model":{"display_name":"Test"},"workspace":{"current_dir":"/tmp"},"context_window":{"used_percentage":72}}'
if printf '%s' "$sample" | bash "$CLAUDE_DIR/statusline.sh" 2>/dev/null | grep -q 'context: 72% used'; then
  ok "statusline.sh 有正常輸出"
else
  warn "statusline.sh 沒有輸出預期的內容"
  failed=$((failed + 1))
fi
[ "$failed" = 0 ] || fail "驗證有 $failed 項未通過"

step "完成！"
echo "    新開終端機分頁後執行 claude，第一次會開啟瀏覽器登入帳號"
