#!/usr/bin/env bash
# TaskPeace MCP installer — one-shot setup for Claude Code / Cursor.
#
# Usage:
#   curl -fsSL https://taskpeace.com/install.sh | bash        (prompts for your token)
#   PROMPTPRIO_API_TOKEN=pp_xxx bash <(curl -fsSL https://taskpeace.com/install.sh)
#
# What it does:
#   1. Verifies your token against /api/auth/me
#   2. Downloads the bundled MCP server to ~/.local/share/promptprio/mcp-server.js
#   3. Registers a `promptprio` server with Claude Code — via the `claude` CLI
#      (canonical, writes ~/.claude.json) or by merging ~/.claude.json directly.
#      Also configures Cursor (~/.cursor/mcp.json) when present.
#   4. Installs the one-key resume: a marked block in ~/.claude/CLAUDE.md so a bare `q`
#      resumes a stopped autopilot run in EVERY session (opt out: TASKPRIO_NO_RESUME_KEY=1).
#
# Re-run safely — idempotent. Updates the server binary + refreshes the config entry + resume block.

set -euo pipefail

API="${PROMPTPRIO_API_URL:-https://taskprio.com}"
SITE="https://taskpeace.com"          # human-facing site (API base stays taskprio.com for installed agents)
TOKEN="${PROMPTPRIO_API_TOKEN:-${1:-}}"
CACHE_DIR="$HOME/.local/share/promptprio"
MCP_BIN="$CACHE_DIR/mcp-server.js"
CLAUDE_JSON="$HOME/.claude.json"     # Claude Code reads MCP servers from HERE (not ~/.claude/mcp.json)
CURSOR_JSON="$HOME/.cursor/mcp.json"

red() { printf '\033[31m%s\033[0m\n' "$*"; }
grn() { printf '\033[32m%s\033[0m\n' "$*"; }
dim() { printf '\033[2m%s\033[0m\n' "$*"; }

printf '\033[1mTaskPeace MCP installer\033[0m\n'
dim "  $SITE"
echo

# No token in env/arg? Ask for it interactively (works under `curl | bash` via /dev/tty).
if [ -z "$TOKEN" ] && [ -e /dev/tty ] && [ -r /dev/tty ]; then
  printf 'Paste your token (%s → sidebar foot → Copy token): ' "$SITE" > /dev/tty
  IFS= read -rs TOKEN < /dev/tty || TOKEN=""
  printf '\n' > /dev/tty
fi

if [ -z "$TOKEN" ]; then
  red "No token provided."
  echo "Get yours at $SITE → sidebar foot → Copy token, then run:"
  echo "  PROMPTPRIO_API_TOKEN=pp_xxx bash <(curl -fsSL $SITE/install.sh)"
  echo
  dim "Tip: prefix the command with a space to keep the token out of shell history."
  exit 1
fi

# 1. Verify token + identity
printf 'Verifying token... '
ME_JSON=$(curl -fsS -H "Authorization: Bearer $TOKEN" "$API/api/auth/me" 2>/dev/null || true)
WHO=$(printf '%s' "$ME_JSON" | python3 -c "import json,sys
try:
  d=json.load(sys.stdin); u=d.get('user') or {}
  print((u.get('email') or '')+'|'+(u.get('plan') or ''))
except Exception:
  print('|')" 2>/dev/null || echo '|')
EMAIL="${WHO%|*}"
PLAN="${WHO#*|}"
if [ -z "$EMAIL" ]; then
  red "FAILED"
  echo "Token rejected by $API/api/auth/me. Check it at $SITE → sidebar foot → Copy token."
  exit 1
fi
grn "ok — $EMAIL · $PLAN"

# 2. Download bundled server (atomic via tmp + mv)
printf 'Downloading server... '
mkdir -p "$CACHE_DIR"
curl -fsSL "$API/mcp-server.js" -o "$MCP_BIN.tmp"
mv "$MCP_BIN.tmp" "$MCP_BIN"
chmod +x "$MCP_BIN"
SIZE=$(wc -c < "$MCP_BIN" | tr -d ' ')
grn "ok — $((SIZE/1024)) KB at $MCP_BIN"

# 3. Register the server with the agent(s) on this machine.
#    IMPORTANT: Claude Code reads MCP servers from ~/.claude.json (managed by the
#    `claude` CLI) — NOT ~/.claude/mcp.json. Prefer the CLI (version-proof); fall
#    back to merging ~/.claude.json directly when the CLI isn't on PATH.
SERVER_JSON=$(python3 -c "import json,sys;print(json.dumps({'command':'node','args':[sys.argv[1]],'env':{'PROMPTPRIO_API_TOKEN':sys.argv[2],'PROMPTPRIO_API_URL':sys.argv[3]}}))" "$MCP_BIN" "$TOKEN" "$API")

# helper: merge the promptprio entry into a Claude/Cursor mcp.json-shaped file
merge_cfg() {
  python3 - "$1" "$SERVER_JSON" <<'PY'
import json, os, sys
path, server = sys.argv[1], json.loads(sys.argv[2])
d = os.path.dirname(path)
if d:
    os.makedirs(d, exist_ok=True)
cfg = {}
if os.path.exists(path):
    try:
        with open(path) as f:
            txt = f.read().strip()
        cfg = json.loads(txt) if txt else {}
    except json.JSONDecodeError:
        os.rename(path, path + '.bak')  # back up rather than clobber
        cfg = {}
cfg.setdefault('mcpServers', {})['promptprio'] = server
with open(path, 'w') as f:
    json.dump(cfg, f, indent=2)
    f.write('\n')
PY
}

printf 'Registering with Claude Code... '
CC_OK=""
if command -v claude >/dev/null 2>&1; then
  claude mcp remove promptprio --scope user >/dev/null 2>&1 || true
  if claude mcp add-json promptprio "$SERVER_JSON" --scope user >/dev/null 2>&1; then
    grn "ok — via claude CLI (user scope)"; CC_OK=1
  fi
fi
if [ -z "$CC_OK" ]; then
  merge_cfg "$CLAUDE_JSON"
  grn "ok — merged into ~/.claude.json"
fi

# Cursor: only if it's installed on this machine.
if [ -d "$HOME/.cursor" ]; then
  printf 'Registering with Cursor... '
  merge_cfg "$CURSOR_JSON"
  grn "ok — $CURSOR_JSON"
fi

# 4. One-key resume — teach the agent what `q` means in EVERY session (not only when the
#    autopilot prompt is pasted), so a bare `q` resumes a stopped run cold (fresh session or
#    after compaction). The block is marked, idempotent, and written ATOMICALLY with a one-time
#    .bak into ~/.claude/CLAUDE.md (Claude Code) + ./AGENTS.md when present (cross-tool: Cursor /
#    Codex / Gemini). Controls: TASKPRIO_NO_RESUME_KEY=1 skip · TASKPRIO_RESUME_KEY=1 install
#    without asking. On a terminal it asks first (default yes). Remove the block to undo.

# idempotently upsert the resume-key block into $1 — atomic, version-agnostic, .bak once.
write_resume_block() {
  RB_TARGET="$1" python3 <<'PY'
import os, re, tempfile
path = os.environ["RB_TARGET"]
body = (
  "## TaskPeace — one-key resume (`q`)\n"
  "When my whole message is exactly `q` (or `Q` / `continue`) — case-insensitive, on its own — "
  "RESUME the TaskPeace autopilot: re-orient first (recover any in-flight task, make the board true "
  "with the `promptprio` MCP tools, never redo shipped work), then resume the loop "
  "(get_next_task → do it → verify → complete_task → repeat), auto-scoped to this session's "
  "working directory. Canonical spec: https://taskprio.com/continue-prompt.txt — fetch + follow it "
  "if the loop isn't already in context. Lead your reply with `▶ q · TaskPeace autopilot — resuming…` "
  "so I see it landed. `q <text>` scopes the run (e.g. `q ship readstacks`). Only `stop` / `pause` / "
  "`halt` ends a run. Bare `q` fires ONLY when it is essentially the whole message (\"add a q param\" "
  "does NOT). If this session has no `promptprio` MCP tools / no TaskPeace queue, treat `q` literally.\n"
)
block = "<!-- taskprio:resume-key v2 -->\n" + body + "<!-- /taskprio:resume-key -->"
try:
    with open(path, encoding="utf-8") as f: txt = f.read()
except FileNotFoundError:
    txt = ""
# match ANY prior version of the block, so re-installs upgrade it in place (never duplicate)
pat = re.compile(r"<!--\s*taskprio:resume-key\b.*?-->.*?<!--\s*/taskprio:resume-key\s*-->", re.S)
if pat.search(txt):
    new = pat.sub(lambda _m: block, txt)
else:
    if txt.strip() and not os.path.exists(path + ".bak"):
        try:
            with open(path + ".bak", "w", encoding="utf-8") as b: b.write(txt)
        except OSError:
            pass
    new = (txt.rstrip() + "\n\n" if txt.strip() else "") + block + "\n"
if new == txt:
    raise SystemExit(0)  # already current — nothing to write
# atomic: write a temp file in the same dir, then replace — never truncate the live file
d = os.path.dirname(path) or "."
fd, tmp = tempfile.mkstemp(dir=d, prefix=".taskprio-rk-")
try:
    with os.fdopen(fd, "w", encoding="utf-8") as f: f.write(new)
    os.replace(tmp, path)
except BaseException:
    try: os.unlink(tmp)
    except OSError: pass
    raise
PY
}

# decide whether to install (env wins; else ask on a terminal; else default on + announce)
RK_DO=yes
if [ "${TASKPRIO_NO_RESUME_KEY:-}" = "1" ]; then
  RK_DO=no
elif [ "${TASKPRIO_RESUME_KEY:-}" != "1" ] && [ -r /dev/tty ] && [ -w /dev/tty ]; then
  printf 'Add one-key resume — type \033[1mq\033[0m to continue a stopped run in any session? [Y/n] ' > /dev/tty
  RK_ANS=""; read -r RK_ANS < /dev/tty || true
  case "$RK_ANS" in [Nn]*) RK_DO=no ;; esac
fi

if [ "$RK_DO" = no ]; then
  dim "One-key resume (q): skipped — enable later by re-running with TASKPRIO_RESUME_KEY=1."
else
  RK_DONE=""
  # Claude Code global memory — only when Claude Code is actually in use here.
  if command -v claude >/dev/null 2>&1 || [ -d "$HOME/.claude" ] || [ "${CC_OK:-}" = "1" ]; then
    printf 'Installing one-key resume (q)... '
    mkdir -p "$HOME/.claude"
    if write_resume_block "$HOME/.claude/CLAUDE.md"; then
      grn "ok — ~/.claude/CLAUDE.md (every Claude Code session now knows q)"; RK_DONE=1
    else
      red "skipped — couldn't write ~/.claude/CLAUDE.md"
    fi
  fi
  # Cross-tool — only into an AGENTS.md that already exists where you ran this.
  if [ -f "$PWD/AGENTS.md" ] && write_resume_block "$PWD/AGENTS.md"; then
    grn "ok — $PWD/AGENTS.md (cross-tool resume for this project)"; RK_DONE=1
  fi
  if [ -n "$RK_DONE" ]; then
    dim "  Undo: delete the 'taskprio:resume-key' block (a one-time .bak was kept), or set TASKPRIO_NO_RESUME_KEY=1."
  else
    dim "One-key resume (q): nothing written (no Claude Code memory or ./AGENTS.md found)."
  fi
fi

echo
grn "DONE."
echo "Restart Claude Code (or Cursor), then verify:  claude mcp list   (you should see 'promptprio')"
echo
echo "First steps in your agent:"
echo "  - Look around:         ask \"list my promptprio tasks\""
echo "  - Start the autopilot: paste  https://taskpeace.com/autopilot-prompt.txt  into your agent,"
echo "                         inside a project's repo — it pulls the top-priority task, works it,"
echo "                         verifies, and clears the queue top to bottom, autonomously."
echo "  - Resume anytime:      type  q  on its own — it picks a stopped run back up (re-orient + continue)."
echo
dim "Remove: claude mcp remove promptprio --scope user  (or delete the 'promptprio' entry from the config)."
