#!/bin/sh
# Install (or update) Orchester on macOS or Linux, for this user.
#
#   curl -fsSL https://get.mural.ink/install.sh | sh
#
# No sudo, no git, no system Node, no account. A helper that manages the services of
# this machine: it brings its own Node, starts with nothing running and leaves the
# `orchester` command. Running it again updates to the latest release.
#
#   ORCHESTER_BASE   where releases live      (default https://get.mural.ink)
#   ORCHESTER_ROOT   where it installs        (default ~/.orchester)
#   ORCHESTER_NO_SERVICE=1   do not start with the system
#   ORCHESTER_AUTOSTART=a,b  what starts with it (default: what it already started; empty when new)
#   ORCHESTER_NO_SHORTCUT=1  no «Orchester» app icon (it opens the control panel)
#   ORCHESTER_NO_OPEN=1      do not open the control panel at the end
#   ORCHESTER_UPDATE=1       set by the orchester when it updates itself (src/update.ts):
#                            no questions, no browser
#
#   ~/.orchester/node/            its own Node
#   ~/.orchester/releases/<v>/    each release; current → the active one
#   ~/.orchester/home/            ELIO_HOME: account link, services, state
#   ~/.orchester/logs/
#   ~/.local/bin/orchester        the command
#   ~/Applications/Orchester.app  (macOS) · ~/.local/share/applications/orchester.desktop (Linux)
set -eu

BASE="${ORCHESTER_BASE:-https://get.mural.ink}"
BASE="${BASE%/}"
[ "$BASE" = https://get.mural.ink ] || export ORCHESTER_BASE="$BASE"
ROOT="${ORCHESTER_ROOT:-$HOME/.orchester}"
# Where the command goes; an update (or a second run) keeps the first install's choice.
BIN_DIR="${ORCHESTER_BIN_DIR:-$(cat "$ROOT/.bin-dir" 2>/dev/null || true)}"
BIN_DIR="${BIN_DIR:-$HOME/.local/bin}"

say()  { printf '%s\n' "$*"; }
step() { printf '\n▸ %s\n' "$*"; }
die()  { printf '✗ %s\n' "$*" >&2; exit 1; }
need() { command -v "$1" >/dev/null 2>&1 || die "falta «$1»: instálalo y vuelve a probar"; }
sha256() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1; else shasum -a 256 "$1" | cut -d' ' -f1; fi; }
# A bar for a person at a terminal; nothing for a log (the self-update writes one).
if [ -t 2 ]; then BAR=--progress-bar; else BAR=-sS; fi
json() { sed -n "s/.*\"$1\": *\"\{0,1\}\([^\",]*\)\"\{0,1\}.*/\1/p" "$2" | head -1; }

need curl; need tar
OS="$(uname -s)"; ARCH="$(uname -m)"
case "$OS" in
  Darwin) NODE_OS=darwin; EXT=tar.gz ;;
  Linux)  NODE_OS=linux;  EXT=tar.xz; need xz ;;
  *) die "este instalador es para macOS y Linux. En Windows: irm $BASE/install.ps1 | iex" ;;
esac
case "$ARCH" in
  x86_64|amd64) NODE_ARCH=x64 ;;
  arm64|aarch64) NODE_ARCH=arm64 ;;
  *) die "procesador no soportado: $ARCH" ;;
esac

TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT INT TERM
mkdir -p "$ROOT/releases" "$ROOT/home" "$ROOT/logs" "$BIN_DIR"

step "Última versión ($BASE)"
curl -fsSL "$BASE/orchester/latest.json" -o "$TMP/latest.json" || die "no llego a $BASE"
VERSION="$(json version "$TMP/latest.json")"
FILE="$(json file "$TMP/latest.json")"
SHA="$(json sha256 "$TMP/latest.json")"
NODE_VERSION="$(json node "$TMP/latest.json")"
[ -n "$VERSION" ] && [ -n "$FILE" ] && [ -n "$SHA" ] && [ -n "$NODE_VERSION" ] || die "latest.json incompleto"
say "  orchester $VERSION · node $NODE_VERSION"

# ── Node ──────────────────────────────────────────────────────────────────────
NODE_DIR="$ROOT/node"
NODE="$NODE_DIR/bin/node"
if [ "$("$NODE" -v 2>/dev/null || true)" != "v$NODE_VERSION" ]; then
  step "Node $NODE_VERSION ($NODE_OS-$NODE_ARCH)"
  NAME="node-v$NODE_VERSION-$NODE_OS-$NODE_ARCH.$EXT"
  curl -fsSL "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt" -o "$TMP/SHASUMS256.txt"
  curl -fSL $BAR "https://nodejs.org/dist/v$NODE_VERSION/$NAME" -o "$TMP/$NAME"
  WANT="$(grep " $NAME\$" "$TMP/SHASUMS256.txt" | cut -d' ' -f1)"
  [ -n "$WANT" ] && [ "$(sha256 "$TMP/$NAME")" = "$WANT" ] || die "la huella de $NAME no cuadra: no lo instalo"
  rm -rf "$NODE_DIR.new" && mkdir -p "$NODE_DIR.new"
  tar -xf "$TMP/$NAME" -C "$NODE_DIR.new" --strip-components 1
  rm -rf "$NODE_DIR" && mv "$NODE_DIR.new" "$NODE_DIR"
fi
export PATH="$NODE_DIR/bin:$PATH"

# ── the release ───────────────────────────────────────────────────────────────
REL="$ROOT/releases/$VERSION"
if [ ! -f "$REL/.installed" ]; then
  step "Descargando orchester $VERSION"
  curl -fSL $BAR "$BASE/orchester/$FILE" -o "$TMP/orchester.tar.gz"
  [ "$(sha256 "$TMP/orchester.tar.gz")" = "$SHA" ] || die "la huella del paquete no cuadra: no lo instalo"
  rm -rf "$REL" && mkdir -p "$REL"
  tar -xzf "$TMP/orchester.tar.gz" -C "$REL" --strip-components 1
  step "Instalando dependencias (unos minutos la primera vez)"
  # Only what the headless orchester and its core use: not the Electron app
  # (node-pty has no Linux binaries and would need a compiler).
  ( cd "$REL" && ELECTRON_SKIP_BINARY_DOWNLOAD=1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 PUPPETEER_SKIP_DOWNLOAD=1 \
      "$NODE_DIR/bin/npm" ci --no-audit --no-fund --loglevel=error \
        --workspace @muralink/orchester --workspace @muralink/platform-server --include-workspace-root ) \
    || die "npm ci ha fallado (mira el mensaje de arriba)"
  "$NODE" -e "new (require('$REL/node_modules/better-sqlite3'))(':memory:')" || die "sqlite no carga en este equipo"
  touch "$REL/.installed"
fi
# -n: replace the link itself. (mv current.new current would move it INTO the old release.)
ln -sfn "$REL" "$ROOT/current"

# ── the command ───────────────────────────────────────────────────────────────
cat > "$BIN_DIR/orchester" <<EOF
#!/bin/sh
# Written by the orchester installer. Re-run the installer to update.
export ELIO_HOME="\${ELIO_HOME:-$ROOT/home}"
export MURALINK_LOG_DIR="\${MURALINK_LOG_DIR:-$ROOT/logs}"
export MURALINK_NODE="$NODE"
export ELIO_BIND_HOST="\${ELIO_BIND_HOST:-127.0.0.1}"
$( [ "$BASE" != https://get.mural.ink ] && printf 'export ORCHESTER_BASE="${ORCHESTER_BASE:-%s}"' "$BASE" )
exec "$NODE" "$ROOT/current/packages/orchester/bin/orchester.mjs" "\$@"
EOF
chmod 755 "$BIN_DIR/orchester"
printf '%s\n' "$BIN_DIR" > "$ROOT/.bin-dir"

# ── start with the system (empty: nothing but the account link) ──────────────
if [ "${ORCHESTER_NO_SERVICE:-0}" != 1 ]; then
  step "Arranque con el sistema"
  case "$OS" in
    Darwin) SVC="--launchd agent --name ink.mural.orchesterd"; UNIT="$HOME/Library/LaunchAgents/ink.mural.orchesterd.plist" ;;
    Linux)  SVC="--scope user"; UNIT="$HOME/.config/systemd/user/muralink-orchesterd.service" ;;
  esac
  # An update keeps what this orchester already starts (OpenWA on gamma…): the
  # service file is rewritten, so read its ELIO_AUTOSTART first. Empty on a new install.
  if [ -z "${ORCHESTER_AUTOSTART+x}" ] && [ -f "$UNIT" ]; then
    ORCHESTER_AUTOSTART="$(sed -n -e 's/^Environment="\{0,1\}ELIO_AUTOSTART=\([^"]*\)"\{0,1\}$/\1/p' \
      -e 's/.*<key>ELIO_AUTOSTART<\/key><string>\(.*\)<\/string>.*/\1/p' \
      -e '/<key>ELIO_AUTOSTART<\/key>$/{n;s/.*<string>\(.*\)<\/string>.*/\1/p;}' "$UNIT" | head -1)"
  fi
  # shellcheck disable=SC2086
  "$BIN_DIR/orchester" service install --yes $SVC --home "$ROOT/home" --log-dir "$ROOT/logs" \
    --autostart "${ORCHESTER_AUTOSTART:-}" --node "$NODE" --repo "$ROOT/current" >"$ROOT/logs/service-install.log" 2>&1 \
    && { [ "$OS" = Linux ] && systemctl --user restart muralink-orchesterd.service >/dev/null 2>&1 || true; } \
    && say "  ✓ arranca solo al encender (detalle en $ROOT/logs/service-install.log)" \
    || say "  ! no he podido dejarlo como servicio: mira $ROOT/logs/service-install.log"
fi

# ── the icon: «Orchester» opens the control panel (which says when there is a new version) ──
if [ "${ORCHESTER_NO_SHORTCUT:-0}" != 1 ]; then
  case "$OS" in
    Darwin)
      APP="$HOME/Applications/Orchester.app"
      mkdir -p "$APP/Contents/MacOS"
      cat > "$APP/Contents/Info.plist" <<'PLIST'
<?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>CFBundleName</key><string>Orchester</string>
  <key>CFBundleDisplayName</key><string>Orchester</string>
  <key>CFBundleIdentifier</key><string>ink.mural.orchester.panel</string>
  <key>CFBundleExecutable</key><string>Orchester</string>
  <key>CFBundlePackageType</key><string>APPL</string>
  <key>CFBundleShortVersionString</key><string>1</string>
  <key>LSUIElement</key><true/>
</dict></plist>
PLIST
      cat > "$APP/Contents/MacOS/Orchester" <<APP
#!/bin/sh
# Written by the orchester installer: opens the control panel in the browser.
exec "$BIN_DIR/orchester" panel >/dev/null 2>&1
APP
      chmod 755 "$APP/Contents/MacOS/Orchester"
      ;;
    Linux)
      DESK="$HOME/.local/share/applications"
      mkdir -p "$DESK"
      cat > "$DESK/orchester.desktop" <<DESKTOP
[Desktop Entry]
Type=Application
Name=Orchester
Comment=Gestiona los servicios de este equipo
Exec="$BIN_DIR/orchester" panel
Icon=network-server
Terminal=false
Categories=System;Utility;
DESKTOP
      ;;
  esac
fi

# old releases: keep the active one and the one before
ls -1t "$ROOT/releases" 2>/dev/null | tail -n +3 | while read -r old; do
  [ "$ROOT/releases/$old" = "$REL" ] || rm -rf "$ROOT/releases/$old"
done

step "Listo: orchester $VERSION"
[ "${ORCHESTER_UPDATE:-0}" = 1 ] && exit 0
case ":$PATH:" in *":$BIN_DIR:"*) ;; *) say "  Añade $BIN_DIR a tu PATH (o usa $BIN_DIR/orchester)." ;; esac
case "$OS" in
  Darwin) say "  Ábrelo cuando quieras: «Orchester» en Aplicaciones, o: orchester panel" ;;
  Linux)  say "  Ábrelo cuando quieras: «Orchester» en tus aplicaciones, o: orchester panel" ;;
esac
# The service has just (re)started the daemon: give it a moment before asking.
i=0; while [ $i -lt 20 ] && ! "$BIN_DIR/orchester" status --json 2>/dev/null | grep -q '"daemon": true'; do sleep 0.5; i=$((i+1)); done
"$BIN_DIR/orchester" status || true
say ""
say "  Qué instalar: orchester packages  (por ejemplo: orchester add hosting)"
# A desktop: open the control panel now (headless servers have no browser).
if [ "${ORCHESTER_NO_OPEN:-0}" != 1 ] && { [ "$OS" = Darwin ] || [ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ]; }; then
  "$BIN_DIR/orchester" panel >/dev/null 2>&1 || true
fi
