#!/usr/bin/env bash
#
# install.sh: install the `solomon` CLI on macOS (Apple Silicon).
#
#   curl -fsSL https://so.lomon.dev/install.sh | bash
#
# Served as a static file from web/public/install.sh by the `solomon-web`
# Vercel project (files under web/public/ are served at the site root), so it
# ships with the marketing site's deploy and needs no route and no server code.
#
# ---------------------------------------------------------------------------
# WHERE THE BINARY COMES FROM, and why it is not the GitHub API
# ---------------------------------------------------------------------------
# `pnthn-ai/solomon-editor` is a PRIVATE repository. `api.github.com/repos/
# pnthn-ai/solomon-editor/releases/latest` answers 404 to anyone without a
# token, and so does every `browser_download_url` on a release asset. An
# installer built on those URLs would be a dead end for exactly the stranger it
# exists to serve: it would print "no release found" on a machine where a
# release very much exists.
#
# So this script uses the two PUBLIC, unauthenticated endpoints the desktop
# auto-updater already depends on, both served by the Solomon web app:
#
#   1. GET /api/desktop/update/manifest
#        → the published desktop release manifest. Its `.version` is the
#          version of the newest PUBLISHED release (drafts never reach it: the
#          manifest is pushed by publish-update-manifest.yml, which fires on
#          `release: published`). That version is the tag, prefixed with `v`.
#   2. GET /api/desktop/update/download?tag=<tag>&asset=<basename>
#        → a server-side proxy that resolves the release asset with a
#          server-held GitHub token and streams the bytes back. This is the
#          route that exists precisely because the repo is private.
#
# Consequence, stated rather than implied: this script can only install a CLI
# from a release that has been PUBLISHED. A draft release is invisible to it,
# which is the correct behaviour, not a bug to work around.
#
# ---------------------------------------------------------------------------
# SECURITY POSTURE: the honest version
# ---------------------------------------------------------------------------
# The tarball's SHA-256 is checked against `checksums.txt` from the same
# release, and a mismatch is fatal. That defends INTEGRITY: truncated
# downloads, corrupted bytes, a CDN mangling the stream. It is NOT provenance:
# both files come from the same origin, so anyone who could tamper with one
# could tamper with the other. Provenance here rests on TLS to
# `app.lomon.dev` and on the fact that you chose to run this script. No
# claim is made beyond that.
#
# What this script will NEVER do:
#   * run `sudo`, or install anywhere outside your home directory
#   * modify your shell profile (`.zshrc`, `.bash_profile`, `.profile`, …).
#     If the install directory is not on your PATH it prints the line to add
#     and lets you add it.
#   * install a binary it could not verify, could not unpack, or could not run
#   * install anything at all on a platform the release does not build for
#
# Every failure path exits non-zero with a distinct code and a message naming
# what actually went wrong:
#
#   1  usage / unexpected internal failure
#   2  unsupported platform (not macOS, or not Apple Silicon)
#   3  a required tool is missing (curl, tar, shasum/sha256sum, install)
#   4  could not resolve which version to install
#   5  a download failed
#   6  CHECKSUM MISMATCH: nothing was installed
#   7  the archive did not contain the expected binary
#   8  the downloaded binary did not run on this machine
#
# ---------------------------------------------------------------------------
# ENVIRONMENT
# ---------------------------------------------------------------------------
#   SOLOMON_VERSION      pin a version instead of resolving the latest, e.g.
#                        `SOLOMON_VERSION=0.3.0` or `v0.3.0`
#   SOLOMON_INSTALL_DIR  where to put the binary (default: $HOME/.local/bin)
#   SOLOMON_MANIFEST_URL / SOLOMON_DOWNLOAD_BASE
#                        endpoint overrides. These exist so verify-install-sh.mjs
#                        can drive the real script against a local fixture
#                        server; they are not part of the user-facing contract.
#
# PARTIAL-DOWNLOAD SAFETY: every statement below is a constant or a function
# definition, and the only call is `main "$@"` on the last line. A pipe that
# is cut short therefore executes nothing at all.
#
set -euo pipefail

readonly SOLOMON_REPO_SLUG="pnthn-ai/solomon-editor"
readonly SOLOMON_DEFAULT_MANIFEST_URL="https://app.lomon.dev/api/desktop/update/manifest"
readonly SOLOMON_DEFAULT_DOWNLOAD_BASE="https://app.lomon.dev/api/desktop/update/download"
readonly SOLOMON_CLI_ASSET="solomon-cli-aarch64-apple-darwin.tar.gz"
readonly SOLOMON_CHECKSUMS_ASSET="checksums.txt"
readonly SOLOMON_BIN_NAME="solomon"

readonly EXIT_USAGE=1
readonly EXIT_PLATFORM=2
readonly EXIT_MISSING_TOOL=3
readonly EXIT_RESOLVE=4
readonly EXIT_DOWNLOAD=5
readonly EXIT_CHECKSUM=6
readonly EXIT_ARCHIVE=7
readonly EXIT_SMOKE=8

# --- output -----------------------------------------------------------------

say() { printf '%s\n' "$*"; }
step() { printf '==> %s\n' "$*"; }
warn() { printf 'warning: %s\n' "$*" >&2; }

# Every fatal exit goes through here, so no failure can leave the script
# printing nothing and no failure can exit 0.
die() {
  local code="$1"
  shift
  printf 'error: %s\n' "$*" >&2
  exit "$code"
}

# --- platform ---------------------------------------------------------------

# The release builds Apple Silicon macOS ONLY (.github/workflows/release.yml
# targets aarch64-apple-darwin; Intel macOS was dropped. EOL after macOS 26,
# and the ONNX dependency ships no x86_64-darwin prebuilt). On anything else
# there is no binary to install, so say so honestly.
#
# There used to be a "build it yourself" fallback here
# (`cargo install --git https://github.com/${SOLOMON_REPO_SLUG} solomon-cli`).
# It never worked: ${SOLOMON_REPO_SLUG} is a PRIVATE repository, so `cargo
# install --git` 404s for exactly the outsider this script exists to serve —
# the same reason this script itself resolves the release through
# app.lomon.dev instead of the GitHub API (see the header comment above).
# Printing a command that cannot succeed is worse than printing nothing, so
# this now just says what is true: no build for this platform today, and
# no source fallback either. Linux and Intel Mac builds are a product
# decision nobody has made yet, not a bug in this script.
unsupported_platform() {
  local os="$1" arch="$2"
  say ""
  say "The Solomon CLI only ships for macOS on Apple Silicon (arm64) right now."
  say "There is no prebuilt binary for ${os}/${arch}, and no source fallback:"
  say "the ${SOLOMON_REPO_SLUG} repository is private, so \"cargo install --git\""
  say "cannot resolve it for you either. Linux and Intel Mac builds are not"
  say "available yet."
  say ""
  die "$EXIT_PLATFORM" "unsupported platform: ${os}/${arch}; nothing was installed"
}

check_platform() {
  local os arch
  os="$(uname -s)"
  arch="$(uname -m)"
  if [ "$os" != "Darwin" ]; then
    unsupported_platform "$os" "$arch"
  fi
  if [ "$arch" != "arm64" ] && [ "$arch" != "aarch64" ]; then
    unsupported_platform "$os" "$arch"
  fi
}

require_tools() {
  local missing=()
  local t
  for t in curl tar uname mktemp; do
    command -v "$t" >/dev/null 2>&1 || missing+=("$t")
  done
  if ! command -v shasum >/dev/null 2>&1 && ! command -v sha256sum >/dev/null 2>&1; then
    missing+=("shasum or sha256sum")
  fi
  if [ "${#missing[@]}" -gt 0 ]; then
    die "$EXIT_MISSING_TOOL" "required tool(s) not found: ${missing[*]}"
  fi
}

# --- http -------------------------------------------------------------------

# `--proto '=https'` is applied only to https URLs so the endpoint overrides
# (used by the gate against a local fixture server) still work. Real users
# never hit the other branch: both defaults above are https.
http_get() {
  local url="$1" dest="$2"
  case "$url" in
    https://*) curl -fsSL --proto '=https' --tlsv1.2 --retry 2 --connect-timeout 15 -o "$dest" "$url" ;;
    *) curl -fsSL --retry 2 --connect-timeout 15 -o "$dest" "$url" ;;
  esac
}

# --- version resolution -----------------------------------------------------

# Reads `.version` out of the update manifest without a JSON parser: `jq` is
# not on a stock macOS and requiring it would defeat the one-liner.
manifest_version() {
  local body="$1"
  printf '%s' "$body" \
    | tr ',' '\n' \
    | grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' \
    | head -n 1 \
    | sed -E 's/.*:[[:space:]]*"([^"]+)".*/\1/'
}

resolve_version() {
  local pinned="${SOLOMON_VERSION:-}"
  if [ -n "$pinned" ]; then
    printf '%s' "${pinned#v}"
    return 0
  fi

  local manifest_url="${SOLOMON_MANIFEST_URL:-$SOLOMON_DEFAULT_MANIFEST_URL}"
  local tmp="$1/manifest.json"
  if ! http_get "$manifest_url" "$tmp"; then
    # Covers both "no network" and "the endpoint answered an error", curl has
    # already printed which one it was on stderr, so this names the escape
    # hatch rather than guessing at the cause.
    die "$EXIT_RESOLVE" \
      "could not fetch the release manifest at ${manifest_url} (see curl's message above). Pin a version with SOLOMON_VERSION=x.y.z to skip this lookup"
  fi
  # A 204 (no manifest stored yet) is a 0-byte body with a 0 exit status.
  if [ ! -s "$tmp" ]; then
    die "$EXIT_RESOLVE" \
      "the release manifest at ${manifest_url} is empty: no desktop release has been published yet, so there is no CLI to install"
  fi

  local version
  version="$(manifest_version "$(cat "$tmp")")"
  if [ -z "$version" ]; then
    die "$EXIT_RESOLVE" "could not read a version out of ${manifest_url}"
  fi
  printf '%s' "$version"
}

# --- checksums --------------------------------------------------------------

sha256_of() {
  local file="$1"
  if command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$file" | awk '{print $1}'
  else
    sha256sum "$file" | awk '{print $1}'
  fi
}

# `shasum -a 256 *` writes "<hex>  <name>"; GNU coreutils in binary mode writes
# "<hex>  *<name>". Accept both spellings, and require an EXACT filename match
# so a checksums.txt covering several assets cannot hand back the wrong line.
expected_sha_for() {
  local checksums_file="$1" want="$2"
  awk -v want="$want" '
    {
      name = $2
      sub(/^\*/, "", name)
      if (name == want) { print $1; exit }
    }
  ' "$checksums_file"
}

verify_checksum() {
  local tarball="$1" checksums_file="$2" asset="$3"

  local expected actual
  expected="$(expected_sha_for "$checksums_file" "$asset")"
  if [ -z "$expected" ]; then
    die "$EXIT_CHECKSUM" \
      "${SOLOMON_CHECKSUMS_ASSET} has no entry for ${asset}; refusing to install an unverified binary"
  fi
  actual="$(sha256_of "$tarball")"
  if [ "$expected" != "$actual" ]; then
    say ""
    say "  expected  ${expected}"
    say "  actual    ${actual}"
    say ""
    die "$EXIT_CHECKSUM" \
      "CHECKSUM MISMATCH for ${asset}: the download does not match the published checksum. NOTHING was installed."
  fi
  step "checksum ok (sha256 ${actual})"
}

# --- install ----------------------------------------------------------------

describe_existing() {
  local target="$1"
  if [ -e "$target" ]; then
    local existing
    existing="$("$target" --version 2>/dev/null || true)"
    if [ -n "$existing" ]; then
      say "    replacing the existing install at ${target} (${existing})"
    else
      say "    replacing the existing file at ${target}"
    fi
  fi
}

# Warn (loudly, and by naming both paths) when some OTHER `solomon` earlier
# on PATH will win. This is NOT an installer failure (the file is on disk,
# verified, at the path we printed), so it does not change the exit code; it is
# an environment condition the user has to resolve, and saying "installed!"
# without mentioning it would be the reporting defect this script is built to
# avoid.
report_path_resolution() {
  local target="$1" install_dir="$2"

  local resolved=""
  if command -v "$SOLOMON_BIN_NAME" >/dev/null 2>&1; then
    resolved="$(command -v "$SOLOMON_BIN_NAME")"
  fi

  case ":${PATH}:" in
    *":${install_dir}:"*)
      if [ -n "$resolved" ] && [ "$resolved" != "$target" ]; then
        say ""
        warn "another '${SOLOMON_BIN_NAME}' comes first on your PATH:"
        warn "    ${resolved}   <- this is what typing '${SOLOMON_BIN_NAME}' runs"
        warn "    ${target}   <- what was just installed"
        warn "Remove the first one, or put ${install_dir} earlier on your PATH."
      else
        say ""
        say "Typing '${SOLOMON_BIN_NAME}' now runs ${target}."
      fi
      ;;
    *)
      say ""
      warn "${install_dir} is not on your PATH, so typing '${SOLOMON_BIN_NAME}' will not find it."
      say "Add this line to your shell profile yourself (this script does not edit dotfiles):"
      say ""
      say "    export PATH=\"${install_dir}:\$PATH\""
      say ""
      say "Until then, run it by full path: ${target}"
      ;;
  esac
}

# --- main -------------------------------------------------------------------

# `workdir` is deliberately a GLOBAL, and `cleanup` deliberately guards it with
# `${workdir:-}`. An EXIT trap runs after `main` has returned, so a `local
# workdir` is out of scope by then and `set -u` turns the cleanup into
# "unbound variable", which exits NON-ZERO after a completely successful
# install. That is the "reports failure on success" twin of the defect this
# whole script is careful about, and it is not hypothetical: verify-install-sh.mjs
# caught exactly it on the first run of this file.
workdir=""

cleanup() {
  if [ -n "${workdir:-}" ] && [ -d "${workdir}" ]; then
    rm -rf "${workdir}"
  fi
  return 0
}

usage() {
  say "Install the Solomon CLI (macOS, Apple Silicon)."
  say ""
  say "    curl -fsSL https://so.lomon.dev/install.sh | bash"
  say ""
  say "This script takes no arguments. It is configured with environment variables:"
  say "    SOLOMON_VERSION      install this version instead of the latest (e.g. 0.3.0)"
  say "    SOLOMON_INSTALL_DIR  where to put the binary (default: \$HOME/.local/bin)"
}

main() {
  # No options are supported, so an unrecognised one must not be swallowed:
  # `curl … | bash -s -- --prefix=/usr/local` silently ignoring `--prefix` and
  # installing somewhere else is precisely the quiet wrong answer this script
  # is built to avoid.
  if [ "$#" -gt 0 ]; then
    case "$1" in
      -h | --help)
        usage
        exit 0
        ;;
      *)
        usage >&2
        die "$EXIT_USAGE" "unexpected argument: $1"
        ;;
    esac
  fi

  check_platform
  require_tools

  local install_dir
  if [ -n "${SOLOMON_INSTALL_DIR:-}" ]; then
    install_dir="$SOLOMON_INSTALL_DIR"
  elif [ -n "${HOME:-}" ]; then
    install_dir="${HOME}/.local/bin"
  else
    # `set -u` would otherwise turn this into a bare "HOME: unbound variable".
    die "$EXIT_USAGE" "HOME is not set; pass SOLOMON_INSTALL_DIR to say where the binary should go"
  fi
  local download_base="${SOLOMON_DOWNLOAD_BASE:-$SOLOMON_DEFAULT_DOWNLOAD_BASE}"

  # Registered before the directory exists, `cleanup` copes with that, and
  # this way an interrupt during mktemp is covered too.
  trap cleanup EXIT INT TERM
  workdir="$(mktemp -d "${TMPDIR:-/tmp}/solomon-install.XXXXXX")"

  step "resolving the latest published Solomon release"
  local version tag
  version="$(resolve_version "$workdir")"
  tag="v${version}"

  local target="${install_dir}/${SOLOMON_BIN_NAME}"

  # Say what is about to happen, and where things will land, BEFORE doing any
  # of it.
  say ""
  say "  Solomon CLI installer"
  say "    release       ${tag}"
  say "    asset         ${SOLOMON_CLI_ASSET}"
  say "    source        ${download_base}"
  say "    install to    ${target}"
  say "    sudo          never: nothing outside your home directory is touched"
  say "    shell profile never: if PATH needs a line, it is printed, not written"
  describe_existing "$target"
  say ""

  local tarball="${workdir}/${SOLOMON_CLI_ASSET}"
  local checksums="${workdir}/${SOLOMON_CHECKSUMS_ASSET}"

  step "downloading ${SOLOMON_CHECKSUMS_ASSET}"
  if ! http_get "${download_base}?tag=${tag}&asset=${SOLOMON_CHECKSUMS_ASSET}" "$checksums"; then
    die "$EXIT_DOWNLOAD" \
      "could not download ${SOLOMON_CHECKSUMS_ASSET} for ${tag}. Release ${tag} may not publish a CLI build; see https://so.lomon.dev/download"
  fi

  step "downloading ${SOLOMON_CLI_ASSET}"
  if ! http_get "${download_base}?tag=${tag}&asset=${SOLOMON_CLI_ASSET}" "$tarball"; then
    die "$EXIT_DOWNLOAD" \
      "could not download ${SOLOMON_CLI_ASSET} for ${tag}. Release ${tag} may not publish a CLI build; see https://so.lomon.dev/download"
  fi

  verify_checksum "$tarball" "$checksums" "$SOLOMON_CLI_ASSET"

  step "unpacking"
  local unpacked="${workdir}/unpacked"
  mkdir -p "$unpacked"
  if ! tar -xzf "$tarball" -C "$unpacked"; then
    die "$EXIT_ARCHIVE" "could not unpack ${SOLOMON_CLI_ASSET}"
  fi

  local staged="${unpacked}/${SOLOMON_BIN_NAME}"
  if [ ! -f "$staged" ]; then
    die "$EXIT_ARCHIVE" \
      "${SOLOMON_CLI_ASSET} did not contain a binary named '${SOLOMON_BIN_NAME}' at its top level"
  fi
  chmod +x "$staged"

  # Run it BEFORE installing it. An architecture mismatch, a broken build or a
  # missing dynamic library shows up here, with nothing yet written to the
  # install directory.
  step "checking the downloaded binary runs on this machine"
  local version_line
  if ! version_line="$("$staged" --version 2>&1)"; then
    say "$version_line"
    die "$EXIT_SMOKE" \
      "the downloaded ${SOLOMON_BIN_NAME} binary would not run on this machine. NOTHING was installed"
  fi

  step "installing to ${target}"
  if ! mkdir -p "$install_dir"; then
    die "$EXIT_USAGE" "could not create ${install_dir}"
  fi
  # Copy to a sibling then rename: an interrupted copy can never leave a
  # half-written binary at the path the user is about to run.
  local pending="${target}.new.$$"
  if ! cp "$staged" "$pending"; then
    die "$EXIT_USAGE" "could not write to ${install_dir}; check its permissions"
  fi
  chmod 755 "$pending"
  if ! mv -f "$pending" "$target"; then
    rm -f "$pending"
    die "$EXIT_USAGE" "could not move the new binary into place at ${target}"
  fi

  say ""
  say "Installed ${SOLOMON_BIN_NAME} ${tag} -> ${target}"
  say "    ${version_line}"
  say "    (the CLI reports its own crate version, which is not the ${tag} release tag)"
  report_path_resolution "$target" "$install_dir"
  say ""
  say "Next: solomon --help    ·    docs: https://so.lomon.dev/docs"
}

main "$@"
