#!/bin/sh
# ─── supermux installer ──────────────────────────────────────────────────────
#
#   curl -fsSL https://supermux.dev/install.sh | sh
#
# Interactive by default: asks whether you want the Docker quickstart or a
# native (Linux) install, then walks that path. Reads answers from /dev/tty,
# so the menu works even when piped from curl.
#
# Flags (for automation / no-TTY use):
#   --docker        Docker path, no questions
#   --native        native path, no questions
#   --dir <path>    install directory          (default: ~/supermux)
#   -h, --help      usage + flags
#
# Promises:
#   * never runs sudo on the whole script — only on specific, named package
#     installs, and only after asking you (or never, when non-interactive)
#   * re-running is safe: an existing install is updated, not duplicated
#   * any failure exits with a pointer to the guided SETUP.md path
#
# ─────────────────────────────────────────────────────────────────────────────
set -eu

REPO_URL="https://github.com/UstaLabs/supermux.git"
SETUP_URL="https://github.com/UstaLabs/supermux/blob/main/SETUP.md"
WEB_URL="http://localhost:8787"

MODE=""
DIR=""

usage() {
  cat <<'EOF'
supermux installer — https://supermux.dev

  curl -fsSL https://supermux.dev/install.sh | sh

Interactive by default: asks Docker vs native, then walks that path.

Flags (for automation / no-TTY use):
  --docker        Docker path, no questions
  --native        native path, no questions
  --dir <path>    install directory (default: ~/supermux)
  -h, --help      this text

Promises: sudo only for specific named installs with consent; re-runs update
instead of duplicating; failures point at the guided SETUP.md path.

Usage: install.sh [--docker|--native] [--dir <path>]
EOF
}

say()  { printf '%s\n' "$*"; }
err()  { printf 'install.sh: %s\n' "$*" >&2; }
die()  {
  err "$*"
  say ""
  say "Prefer a guided setup? Paste SETUP.md into Claude Code / ChatGPT and"
  say "it walks you through it:  $SETUP_URL"
  exit 1
}
have() { command -v "$1" >/dev/null 2>&1; }

# ── argument parsing ─────────────────────────────────────────────────────────
while [ $# -gt 0 ]; do
  case "$1" in
    --docker) MODE="docker" ;;
    --native) MODE="native" ;;
    --dir)    shift; [ $# -gt 0 ] || die "--dir needs a path"; DIR="$1" ;;
    -h|--help) usage; exit 0 ;;
    *) err "unknown option: $1"; usage; exit 2 ;;
  esac
  shift
done

# ── interactivity: prompts go through /dev/tty so `curl | sh` stays usable ──
if [ -r /dev/tty ] && [ -w /dev/tty ] && (exec </dev/tty) 2>/dev/null; then
  HAS_TTY=1
else
  HAS_TTY=0
fi
# test hook: the suite forces the non-interactive path so it never prompts
[ "${SUPERMUX_INSTALL_NO_TTY:-}" = "1" ] && HAS_TTY=0

ask() { # $1 prompt, $2 default → sets $REPLY (default when no TTY)
  if [ "$HAS_TTY" -eq 1 ]; then
    printf '%s' "$1" >/dev/tty
    IFS= read -r REPLY </dev/tty || REPLY=""
  else
    REPLY=""
  fi
  [ -n "$REPLY" ] || REPLY="$2"
}

confirm() { # $1 prompt → 0/1 (no TTY ⇒ default no)
  ask "$1 [y/N] " "n"
  case "$REPLY" in y|Y|yes|YES) return 0 ;; *) return 1 ;; esac
}

# ── banner + mode selection ──────────────────────────────────────────────────
say ""
say "  supermux — AFK. Still shipping."
say "  Claude Code, Codex, Cursor & OpenCode, around the clock, on a box you own."
say ""

if [ -z "$MODE" ]; then
  if [ "$HAS_TTY" -eq 0 ]; then
    say "No terminal available to ask questions, and no mode flag given."
    say "Re-run with --docker or --native, e.g.:"
    say ""
    say "  curl -fsSL https://supermux.dev/install.sh | sh -s -- --docker"
    say ""
    exit 2
  fi
  say "How do you want to run supermux?"
  say ""
  say "  [1] Docker  — fastest taste-test; no system changes (recommended)"
  say "  [2] Native  — Linux box with tmux + bun; the always-on setup"
  say ""
  ask "Choice [1/2]: " "1"
  case "$REPLY" in
    2) MODE="native" ;;
    *) MODE="docker" ;;
  esac
fi

[ -n "$DIR" ] || {
  ask "Install directory [$HOME/supermux]: " "$HOME/supermux"
  DIR="$REPLY"
}

# ── shared: clone or update ──────────────────────────────────────────────────
clone_or_update() {
  have git || offer_pkg git || die "git is required"
  if [ -d "$DIR/.git" ]; then
    say "→ Existing install found at $DIR"
    if [ "$HAS_TTY" -eq 1 ]; then
      confirm "  Update it (git pull --ff-only)?" || die "aborted — nothing changed"
    fi
    git -C "$DIR" pull --ff-only || die "update failed (local changes? try: git -C $DIR status)"
  else
    say "→ Cloning supermux into $DIR"
    git clone "$REPO_URL" "$DIR" || die "clone failed"
  fi
}

# ── shared: distro package installs (sudo per package, with consent) ─────────
offer_pkg() { # $1 package → 0 if installed
  PKG="$1"
  if have apt-get;  then INSTALL="sudo apt-get install -y $PKG"
  elif have dnf;    then INSTALL="sudo dnf install -y $PKG"
  elif have pacman; then INSTALL="sudo pacman -S --noconfirm $PKG"
  else return 1; fi
  confirm "→ '$PKG' is missing. Install it now? ($INSTALL)" || return 1
  # shellcheck disable=SC2086  # intentional word-splitting of the command
  $INSTALL || return 1
}

# ── docker path ──────────────────────────────────────────────────────────────
do_docker() {
  if ! have docker; then
    say "→ Docker isn't installed."
    [ "$(uname -s)" = "Linux" ] || die "install Docker Desktop first: https://docs.docker.com/desktop/"
    confirm "  Install it now via Docker's official script (get.docker.com)?" \
      || die "Docker is required for this path (or re-run with --native)"
    curl -fsSL https://get.docker.com | sh || die "Docker install failed"
  fi

  # one decision for how we invoke docker: plain if both compose and the
  # daemon answer unprivileged, sudo if root makes them work, else die
  DOCKER="docker"
  if ! docker compose version >/dev/null 2>&1; then
    sudo docker compose version >/dev/null 2>&1 \
      || die "docker compose v2 not found — see https://docs.docker.com/compose/install/"
    DOCKER="sudo docker"
  elif ! docker info >/dev/null 2>&1; then
    DOCKER="sudo docker"
  fi
  [ "$DOCKER" = "docker" ] \
    || say "→ Using sudo for docker (tip: sudo usermod -aG docker \$USER, then re-login)"

  clone_or_update

  say "→ Starting the broker (docker compose up -d) …"
  ( cd "$DIR" && $DOCKER compose up -d ) || die "docker compose up failed"

  say "→ Waiting for $WEB_URL …"
  i=0 up=0
  while [ "$i" -lt 60 ]; do
    if curl -fsS "$WEB_URL" >/dev/null 2>&1; then up=1; break; fi
    i=$((i+1)); sleep 2
  done

  say ""
  if [ "$up" -eq 1 ]; then
    say "✔ supermux is up:  $WEB_URL"
  else
    say "… $WEB_URL isn't answering yet (a first build can take a while)."
    say "  Watch it come up:  cd $DIR && $DOCKER compose logs -f"
  fi
  say "  Open it in a browser — the setup wizard launches on first visit"
  say "  (the first browser to connect gets paired automatically)."
  say "  Drop the repos you want worked on into: $DIR/workspace/"
}

# ── native path ──────────────────────────────────────────────────────────────
do_native() {
  [ "$(uname -s)" = "Linux" ] \
    || die "the native path is Linux-only — use --docker (or the Docker image) elsewhere"

  have tmux || offer_pkg tmux || die "tmux is required"

  BUN="bun"
  if ! have bun; then
    if [ -x "$HOME/.bun/bin/bun" ]; then
      BUN="$HOME/.bun/bin/bun"
    else
      confirm "→ 'bun' is missing. Install it via the official installer (bun.sh)?" \
        || die "bun is required"
      have bash || offer_pkg bash || die "bash is required by bun's installer"
      curl -fsSL https://bun.sh/install | bash || die "bun install failed"
      BUN="$HOME/.bun/bin/bun"
    fi
    say "→ Using $BUN (add ~/.bun/bin to your PATH for everyday use)"
  fi

  clone_or_update

  say "→ Installing dependencies (bun install) …"
  ( cd "$DIR" && "$BUN" install ) || die "bun install failed"

  # agent CLIs are the user's own logins; just report what's visible
  say ""
  say "→ Agent CLIs on PATH:"
  FOUND=0
  for cli in claude codex cursor-agent opencode; do
    if have "$cli"; then say "    ✔ $cli"; FOUND=1; else say "    – $cli (not found)"; fi
  done
  [ "$FOUND" -eq 1 ] || say "  Install + log into at least one of them before spawning sessions."

  # minimal web-channel env so the broker serves the PWA out of the box
  STATE_DIR="${MUX_STATE_DIR:-$HOME/.mux/state}"
  if [ ! -f "$STATE_DIR/.env" ]; then
    mkdir -p "$STATE_DIR"
    {
      printf 'MUX_WEB_PORT=8787\n'
      printf 'MUX_WEB_PUBLIC_URL=http://localhost:8787\n'
    } > "$STATE_DIR/.env"
    say "→ Wrote $STATE_DIR/.env (web app on $WEB_URL — edit for LAN/proxy URLs)"
  fi

  # optional always-on service (user-level systemd; no sudo needed)
  if have systemctl && confirm "→ Set up the systemd user service (starts on boot)?"; then
    mkdir -p "$HOME/.config/systemd/user"
    # absolute bun path: the systemd --user PATH won't have ~/.bun/bin
    BUN_ABS="$(command -v "$BUN" 2>/dev/null || printf '%s' "$BUN")"
    # escape &, |, \ so the values are safe in the sed replacement below
    DIR_ESC="$(printf '%s' "$DIR" | sed 's/[&|\\]/\\&/g')"
    BUN_ESC="$(printf '%s' "$BUN_ABS" | sed 's/[&|\\]/\\&/g')"
    sed -e "s|__MUX_REPO__|$DIR_ESC|" \
        -e "s|^ExecStart=.*|ExecStart=$BUN_ESC src/main.ts|" \
        "$DIR/systemd/mux.service" \
      > "$HOME/.config/systemd/user/supermux.service"
    systemctl --user daemon-reload
    systemctl --user enable --now supermux
    sleep 2
    if systemctl --user is-active --quiet supermux; then
      say "  ✔ service running (survives reboots with: loginctl enable-linger $USER)"
    else
      say "  ✖ service didn't stay up — inspect: journalctl --user -u supermux -n 50"
    fi
  else
    say ""
    say "Run the broker with:"
    say "  cd $DIR && $BUN src/main.ts"
  fi

  say ""
  say "✔ Native install done:  $WEB_URL"
  say "  First browser to connect gets paired automatically; on a headless box:"
  say "  cd $DIR && $BUN run pair <device-name>"
}

case "$MODE" in
  docker) do_docker ;;
  native) do_native ;;
esac

say ""
say "Docs & issues: https://github.com/UstaLabs/supermux"
