#!/bin/sh
# specci bootstrap installer.
#
# Copyright (c) 2026 Enbloom. MIT licensed — see LICENSE.
# The licence covers this installer, not Specci itself.
#
#   curl -fsSL https://get.specci.ai | SPECCI_TOKEN=<token> sh
#
# This URL is permanent. It goes in the README, the docs and every onboarding
# message, so it is never versioned — the artefacts are versioned instead.
#
# Everything is wrapped in main() and only invoked on the last line. A truncated
# download therefore does nothing at all, rather than executing half an installer.

set -eu

DL_BASE="${SPECCI_DL_BASE:-https://dl.specci.ai}"
CHANNEL="${SPECCI_CHANNEL:-stable}"
PRODUCT="cli"
INSTALL_DIR="${SPECCI_INSTALL_DIR:-$HOME/.local/bin}"

say() { printf '%s\n' "$*"; }
err() { printf 'error: %s\n' "$*" >&2; exit 1; }

need() {
	command -v "$1" >/dev/null 2>&1 || err "'$1' is required but not installed."
}

# Resolve the Rust target triple for this machine. Unsupported platforms fail
# with the list of what does work — a clear refusal beats a confusing 404 later.
detect_target() {
	os="$(uname -s)"
	arch="$(uname -m)"
	case "$os/$arch" in
		Darwin/arm64) printf 'aarch64-apple-darwin' ;;
		Darwin/x86_64)
			err "Intel Macs are not supported yet (this build is Apple Silicon only)." ;;
		Linux/*)
			err "Linux is not supported yet. Currently supported: macOS on Apple Silicon." ;;
		*)
			err "Unsupported platform: $os $arch. Currently supported: macOS on Apple Silicon." ;;
	esac
}

# Minimal field extraction. The manifest is produced by our own release tooling
# and has a fixed, flat shape, so this avoids a jq dependency — macOS ships
# neither jq nor python3. It is deliberately narrow: if the manifest schema
# grows nested objects, this must be revisited rather than patched.
json_string() {
	# $1 = json fragment, $2 = key
	printf '%s' "$1" | sed -n 's/.*"'"$2"'"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1
}

# Isolate one target's object from the "artifacts" map. Relies on those objects
# containing no nested braces, which the schema guarantees.
target_object() {
	printf '%s' "$1" | sed -n 's/.*"'"$2"'"[[:space:]]*:[[:space:]]*{\([^}]*\)}.*/\1/p'
}

sha256_of() {
	if command -v shasum >/dev/null 2>&1; then
		shasum -a 256 "$1" | cut -d' ' -f1
	elif command -v sha256sum >/dev/null 2>&1; then
		sha256sum "$1" | cut -d' ' -f1
	else
		err "no sha256 tool found (need shasum or sha256sum)."
	fi
}

main() {
	need curl
	need tar
	need uname
	need sed

	token="${SPECCI_TOKEN:-}"
	if [ -z "$token" ]; then
		# stdin is the script itself under `curl | sh`, so the terminal is the only
		# place to ask. Writing the prompt *to* /dev/tty rather than stdout means a
		# non-interactive run fails this test silently and falls through to the
		# clear error below, instead of emitting a device error mid-install.
		if { printf 'Invite token: ' > /dev/tty; } 2>/dev/null; then
			read -r token < /dev/tty 2>/dev/null || token=""
		fi
	fi
	[ -n "$token" ] || err "no token supplied. Set SPECCI_TOKEN, or run in a terminal to be prompted."

	target="$(detect_target)"
	say "Resolving specci for $target …"

	manifest_url="$DL_BASE/latest.json?product=$PRODUCT&channel=$CHANNEL"
	manifest="$(curl -fsSL -H "Authorization: Bearer $token" "$manifest_url" 2>/dev/null)" || {
		# A failed manifest fetch is nearly always the token, so say so rather
		# than surfacing a bare HTTP code.
		err "could not read the release manifest. Check the token is valid and not revoked."
	}

	version="$(json_string "$manifest" version)"
	[ -n "$version" ] || err "malformed manifest: no version."

	entry="$(target_object "$manifest" "$target")"
	[ -n "$entry" ] || err "no $version build published for $target."

	path="$(json_string "$entry" path)"
	want_sha="$(json_string "$entry" sha256)"
	[ -n "$path" ] || err "malformed manifest: no path for $target."
	[ -n "$want_sha" ] || err "malformed manifest: no sha256 for $target."

	tmp="$(mktemp -d)"
	# shellcheck disable=SC2064
	trap "rm -rf '$tmp'" EXIT INT TERM

	say "Downloading specci $version …"
	curl -fsSL -H "Authorization: Bearer $token" -o "$tmp/specci.tar.gz" "$DL_BASE$path" \
		|| err "download failed."

	got_sha="$(sha256_of "$tmp/specci.tar.gz")"
	[ "$got_sha" = "$want_sha" ] || err "checksum mismatch — expected $want_sha, got $got_sha. Refusing to install."

	tar -xzf "$tmp/specci.tar.gz" -C "$tmp" || err "could not extract the archive."
	[ -f "$tmp/bin/specci" ] || err "archive did not contain bin/specci."

	mkdir -p "$INSTALL_DIR"
	# Everything in bin/ is installed, so the package can grow a second binary
	# without this script changing. Each lands via a temporary name and a rename,
	# so an interrupted install cannot leave a half-written file where a working
	# one used to be.
	installed=''
	for src in "$tmp"/bin/*; do
		[ -f "$src" ] || continue
		name="$(basename "$src")"
		chmod 755 "$src"
		mv "$src" "$INSTALL_DIR/.$name.new"
		mv "$INSTALL_DIR/.$name.new" "$INSTALL_DIR/$name"
		installed="$installed $name"
	done

	say ""
	say "Installed specci $version to $INSTALL_DIR ($(printf '%s' "$installed" | sed 's/^ //'))"

	case ":$PATH:" in
		*":$INSTALL_DIR:"*) say "Run 'specci --help' to get started." ;;
		*)
			say ""
			say "$INSTALL_DIR is not on your PATH. Add it:"
			say "    export PATH=\"$INSTALL_DIR:\$PATH\""
			;;
	esac
}

main "$@"
