#!/bin/sh
# Mainframe on a new computer, in one command (macOS and Linux; ADRs 0248, 0261; served by the api):
#
#   curl -fsSL https://get.factions.si/install | sh
#
# What it does, per user, with no sudo:
#   1. uv (Astral's installer), unless it is already here
#   2. kwt, Mainframe's per-machine runtime, from a sha256-checked wheel
#   3. Claude Code (Anthropic's installer), unless it is already here
#   4. kwt at login (a systemd user unit, or a launchd agent on macOS)
#   5. a link code: you approve this computer in the app, then `kwt setup` walks your own
#      sign-ins one at a time (Claude Code; optionally Codex and GitHub) and your first agent says hi
#
# Re-running it updates. `sh install.sh --uninstall` removes what it added (your logins stay).
# Options: --yes (no questions; also NONINTERACTIVE=1), --no-agents, --no-link, --dry-run.
# Environment: MAINFRAME_API (the account's api), MAINFRAME_DIST (where kwt's wheel and SHA256SUMS live).
# The api fills in both defaults when it serves this file (install.go); a copy run from the repo needs MAINFRAME_API.
set -eu

MAINFRAME_API=${MAINFRAME_API:-https://api.mainframe.kingdom.quest}
case "$MAINFRAME_API" in __*) echo "Run this from https://get.factions.si/install, or set MAINFRAME_API to your Mainframe's api." >&2; exit 2 ;; esac
MAINFRAME_DIST=${MAINFRAME_DIST:-$MAINFRAME_API/dist}
YES=${NONINTERACTIVE:-0}
AGENTS=1
LINK=1
DRY=0
UNINSTALL=0
for a in "$@"; do
  case "$a" in
    --yes|-y) YES=1 ;;
    --no-agents) AGENTS=0 ;;
    --no-link) LINK=0 ;;
    --dry-run) DRY=1 ;;
    --uninstall) UNINSTALL=1 ;;
    *) echo "unknown option: $a" >&2; exit 2 ;;
  esac
done

BIN="$HOME/.local/bin"
PATH="$BIN:$PATH"
export PATH
# kwt is on loopback; a proxy (corporate, or this proof's) must never carry those calls.
no_proxy="127.0.0.1,localhost${no_proxy:+,$no_proxy}"
NO_PROXY="$no_proxy"
export no_proxy NO_PROXY
if [ -t 1 ]; then B=$(printf '\033[1m'); D=$(printf '\033[2m'); G=$(printf '\033[32m'); Y=$(printf '\033[33m'); R=$(printf '\033[0m')
else B=; D=; G=; Y=; R=; fi
say()  { printf '%s\n' "$*"; }
step() { printf '\n%s==> %s%s\n' "$B" "$*" "$R"; }
ok()   { printf '    %s✓%s %s\n' "$G" "$R" "$*"; }
note() { printf '    %s%s%s\n' "$D" "$*" "$R"; }
warn() { printf '    %s!%s %s\n' "$Y" "$R" "$*"; }
run()  { if [ "$DRY" = 1 ]; then note "would run: $*"; else "$@"; fi; }
have() { command -v "$1" >/dev/null 2>&1; }

OS=$(uname -s)
ARCH=$(uname -m)
case "$OS" in Linux|Darwin) ;; *) say "Mainframe's shell installer is for macOS and Linux. On Windows run:"; \
  say "  irm https://get.factions.si/install | iex"; exit 1 ;; esac
have curl || { say "curl is needed (it fetched this script; install it and run again)."; exit 1; }

if [ "$UNINSTALL" = 1 ]; then
  step "Removing Mainframe from this computer"
  if [ "$OS" = Linux ] && have systemctl; then
    systemctl --user disable --now mainframe-kwt.service 2>/dev/null || true
    rm -f "$HOME/.config/systemd/user/mainframe-kwt.service"
  fi
  if [ "$OS" = Darwin ]; then
    launchctl unload "$HOME/Library/LaunchAgents/quest.kingdom.mainframe.kwt.plist" 2>/dev/null || true
    rm -f "$HOME/Library/LaunchAgents/quest.kingdom.mainframe.kwt.plist"
  fi
  have uv && uv tool uninstall kwt 2>/dev/null || true
  ok "kwt removed. Claude Code, uv and your sign-ins are left as they were."
  note "This computer still shows in Mainframe until you remove it there (Settings > Machines)."
  exit 0
fi

say ""
say "${B}Mainframe${R} will set up this computer ($OS $ARCH, user $(id -un)):"
say ""
say "  1. uv            $(have uv && echo "already here, kept" || echo "Astral's installer, into ~/.local/bin")"
say "  2. kwt           Mainframe's runtime, checked against SHA256SUMS"
if [ "$AGENTS" = 1 ]; then
say "  3. Claude Code   $(have claude && echo "already here, kept" || echo "Anthropic's installer")"
else
say "  3. Claude Code   skipped (--no-agents)"
fi
say "  4. Start kwt at login, on 127.0.0.1:8765 only"
if [ "$LINK" = 1 ]; then
say "  5. Link it (a code you approve in the app), then sign in to your own Claude Code"
fi
say ""
say "  No sudo. Nothing listens beyond this computer. Re-run to update; --uninstall to remove."
say ""
if [ "$YES" != 1 ] && [ "$DRY" != 1 ] && [ -r /dev/tty ] && [ -t 1 ]; then
  printf 'Continue? [Y/n] '
  read -r answer </dev/tty || answer=y
  case "$answer" in n*|N*) say "Nothing was changed."; exit 0 ;; esac
fi

TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT INT TERM

step "1/5 uv"
if have uv; then ok "uv $(uv --version 2>/dev/null | cut -d' ' -f2) already installed"
else
  run sh -c "curl -LsSf https://astral.sh/uv/install.sh | env UV_NO_MODIFY_PATH=1 sh -s -- -q"
  have uv || [ "$DRY" = 1 ] || { warn "uv did not install"; exit 1; }
  ok "uv installed"
fi

step "2/5 kwt"
fetch() { curl -fsSL "$1" -o "$2"; }
fetch "$MAINFRAME_DIST/SHA256SUMS" "$TMP/SHA256SUMS"
WHEEL=$(awk '/kwt-.*\.whl$/ {print $2}' "$TMP/SHA256SUMS" | tail -1)
[ -n "$WHEEL" ] || { warn "no kwt wheel listed in $MAINFRAME_DIST/SHA256SUMS"; exit 1; }
fetch "$MAINFRAME_DIST/$WHEEL" "$TMP/$WHEEL"
WANT=$(awk -v w="$WHEEL" '$2 == w {print $1}' "$TMP/SHA256SUMS")
if have sha256sum; then GOT=$(sha256sum "$TMP/$WHEEL" | cut -d' ' -f1); else GOT=$(shasum -a 256 "$TMP/$WHEEL" | cut -d' ' -f1); fi
[ "$GOT" = "$WANT" ] || { warn "kwt's checksum did not match; nothing installed"; exit 1; }
ok "$WHEEL matches SHA256SUMS (${GOT%"${GOT#????????}"}…)"
run uv tool install --quiet --force --python 3.12 "$TMP/$WHEEL"
[ "$DRY" = 1 ] || ok "kwt $(uv tool list 2>/dev/null | awk '/^kwt / {print $2}') installed ($(command -v kwt))"

step "3/5 Claude Code"
if [ "$AGENTS" = 0 ]; then note "skipped"
elif have claude; then ok "claude already installed"
else
  run sh -c "curl -fsSL https://claude.ai/install.sh | bash -s stable >/dev/null"
  have claude || [ "$DRY" = 1 ] || [ -x "$BIN/claude" ] || warn "Claude Code did not install; Mainframe's setup will offer it again"
  ok "Claude Code installed"
fi
note "Codex, Gemini and the rest are offered in Mainframe's guided setup, only if you want them."

step "4/5 kwt at login"
SUP="$(command -v kwt-supervisor || echo "$BIN/kwt-supervisor")"
started=0
if [ "$OS" = Linux ] && have systemctl && systemctl --user show-environment >/dev/null 2>&1; then
  UNIT="$HOME/.config/systemd/user/mainframe-kwt.service"
  run mkdir -p "$(dirname "$UNIT")"
  if [ "$DRY" != 1 ]; then cat >"$UNIT" <<EOF
[Unit]
Description=kwt, Mainframe's runtime on this computer (127.0.0.1:8765)
After=network-online.target

[Service]
ExecStart=$SUP --no-browser -- --port 8765
Restart=always
RestartSec=3
Environment=PATH=$BIN:/usr/local/bin:/usr/bin:/bin
Environment=MAINFRAME_API=$MAINFRAME_API

[Install]
WantedBy=default.target
EOF
  fi
  run systemctl --user daemon-reload
  run systemctl --user enable mainframe-kwt.service
  run systemctl --user restart mainframe-kwt.service
  ok "systemd user unit mainframe-kwt.service enabled (restarted onto this version)"
  started=1
elif [ "$OS" = Darwin ]; then
  PL="$HOME/Library/LaunchAgents/quest.kingdom.mainframe.kwt.plist"
  run mkdir -p "$(dirname "$PL")"
  if [ "$DRY" != 1 ]; then cat >"$PL" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>quest.kingdom.mainframe.kwt</string>
  <key>ProgramArguments</key><array><string>$SUP</string><string>--no-browser</string><string>--</string><string>--port</string><string>8765</string></array>
  <key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
  <key>EnvironmentVariables</key><dict><key>PATH</key><string>$BIN:/usr/local/bin:/usr/bin:/bin</string></dict>
</dict></plist>
EOF
  fi
  run launchctl unload "$PL" 2>/dev/null || true
  run launchctl load -w "$PL"
  ok "launchd agent loaded (restarted onto this version)"
  started=1
fi
if [ "$started" = 0 ]; then
  warn "no systemd user session here; starting kwt for this session only"
  if pgrep -u "$(id -u)" -f "kwt-supervisor|-m kwt .*--port 8765" >/dev/null 2>&1; then
    run pkill -u "$(id -u)" -f kwt-supervisor || true
    run pkill -u "$(id -u)" -f -- "-m kwt .*--port 8765" || true
    j=0
    while curl -fsS --noproxy '*' http://127.0.0.1:8765/api/health >/dev/null 2>&1 && [ "$j" -lt 15 ]; do sleep 1; j=$((j + 1)); done
    ok "stopped the kwt this script started last time"
  fi
  run sh -c "nohup '$SUP' --no-browser -- --port 8765 >\"\$HOME/.kwt-install.log\" 2>&1 &"
fi
if [ "$DRY" != 1 ]; then
  i=0
  until curl -fsS --noproxy '*' http://127.0.0.1:8765/api/health >/dev/null 2>&1; do
    i=$((i + 1)); [ "$i" -gt 180 ] && { warn "kwt did not answer on 127.0.0.1:8765 within 3 min (log: ~/.kwt-install.log)"; exit 1; }
    sleep 1
  done
  ok "kwt answers on 127.0.0.1:8765 (after ${i} s)"
fi

if [ "$LINK" = 0 ]; then say ""; say "Done. Link it later with: kwt pair"; exit 0; fi
step "5/5 Link this computer to your Mainframe account"
if [ "$DRY" = 1 ]; then note "would run: kwt pair --api-url $MAINFRAME_API"; exit 0; fi
if [ "$YES" = 1 ] || ! [ -t 1 ]; then kwt pair --api-url "$MAINFRAME_API" --no-wait
else kwt pair --api-url "$MAINFRAME_API"; fi
say ""
if [ "$YES" = 1 ] || ! [ -t 1 ] || ! [ -r /dev/tty ]; then
  say "Approve ${B}$(hostname)${R} in the Mainframe app (it shows the code above), then run ${B}kwt setup${R}:"
  say "it signs this computer in to your own Claude Code, one login at a time, and your first agent says hi."
  kwt setup --status || true
  exit 0
fi
step "Your own logins, one at a time"
# curl | sh hands the script on stdin; the sign-ins need the person's terminal.
kwt setup </dev/tty || warn "setup is not finished; run kwt setup any time to pick up where you left off"
