#!/bin/sh
# Mstro installer
# Source:  https://github.com/mstroapp/mstro/blob/main/landing/public/install.sh
# Docs:    https://mstro.app/docs/install
# Run:     curl -fsSL install.mstro.app | sh
#
# Installs the `mstro` CLI globally via your Node package manager (bun, pnpm,
# or npm). Requires Node.js 18+. Does not auto-elevate to sudo. Re-run to
# upgrade.
#
# Environment overrides:
#   MSTRO_INSTALL_PM       npm | pnpm | bun
#   MSTRO_INSTALL_VERSION  npm version spec (default: latest)
#   MSTRO_INSTALL_DRY_RUN  1 to print commands without running them
#   MSTRO_INSTALL_DEBUG    1 to enable shell tracing

# The whole script is wrapped in a single function and invoked on the final
# line. If the connection drops mid-stream, the function definition is
# incomplete and the shell never executes a partial install.
_mstro_install() {
    set -eu

    MSTRO_PKG_NAME="mstro-app"
    MSTRO_BIN_NAME="mstro"
    MSTRO_VERSION="${MSTRO_INSTALL_VERSION:-latest}"

    if [ "${MSTRO_INSTALL_DEBUG:-0}" = "1" ]; then
        set -x
    fi

    _mstro_setup_colors
    _mstro_check_root
    _mstro_detect_platform
    _mstro_detect_node
    _mstro_detect_pm
    _mstro_check_writable_prefix
    _mstro_install_pkg
    _mstro_verify
    _mstro_print_done
}

_mstro_setup_colors() {
    if [ -t 1 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-dumb}" != "dumb" ]; then
        MSTRO_BOLD="$(printf '\033[1m')"
        MSTRO_DIM="$(printf '\033[2m')"
        MSTRO_RED="$(printf '\033[31m')"
        MSTRO_RESET="$(printf '\033[0m')"
    else
        MSTRO_BOLD=""
        MSTRO_DIM=""
        MSTRO_RED=""
        MSTRO_RESET=""
    fi
}

_mstro_info() {
    printf '%s\n' "$1"
}

_mstro_step() {
    printf '%s%s%s\n' "$MSTRO_DIM" "$1" "$MSTRO_RESET"
}

_mstro_fail() {
    printf '%s%s%s\n' "$MSTRO_RED" "$1" "$MSTRO_RESET" >&2
    if [ -n "${2:-}" ]; then
        printf '%s\n' "$2" >&2
    fi
    exit 1
}

# Refuse running as root. A user piping curl|sh to root by mistake is the most
# common way to install something they did not intend to.
_mstro_check_root() {
    _mstro_uid="$(id -u 2>/dev/null || printf '0')"
    if [ "$_mstro_uid" = "0" ]; then
        _mstro_fail \
            "Refusing to run as root." \
            "Run as a regular user. Mstro installs to your user-local Node prefix."
    fi
}

_mstro_detect_platform() {
    _mstro_uname_s="$(uname -s 2>/dev/null || printf 'unknown')"
    case "$_mstro_uname_s" in
        Darwin) MSTRO_OS="macos" ;;
        Linux)  MSTRO_OS="linux" ;;
        *)
            _mstro_fail \
                "Unsupported OS: $_mstro_uname_s" \
                "Mstro supports macOS and Linux (including WSL). For Windows native, use WSL."
            ;;
    esac
}

# Probe Node and require 18+. We do not auto-install Node; we tell the user how.
_mstro_detect_node() {
    if ! command -v node >/dev/null 2>&1; then
        if [ "$MSTRO_OS" = "macos" ]; then
            _mstro_fail \
                "Mstro requires Node.js 18 or newer." \
                "Install Node from https://nodejs.org or run: brew install node"
        else
            _mstro_fail \
                "Mstro requires Node.js 18 or newer." \
                "Install Node from https://nodejs.org, then re-run this script."
        fi
    fi

    _mstro_node_v="$(node --version 2>/dev/null || printf 'v0.0.0')"
    _mstro_node_major="$(printf '%s' "$_mstro_node_v" | sed -e 's/^v//' -e 's/\..*$//')"

    case "$_mstro_node_major" in
        ''|*[!0-9]*)
            _mstro_fail \
                "Could not parse Node version: $_mstro_node_v" \
                "Reinstall Node from https://nodejs.org and re-run this script."
            ;;
    esac

    if [ "$_mstro_node_major" -lt 18 ]; then
        _mstro_fail \
            "Mstro requires Node.js 18 or newer (found $_mstro_node_v)." \
            "Upgrade Node from https://nodejs.org, then re-run this script."
    fi

    _mstro_info "${MSTRO_BOLD}Mstro installer${MSTRO_RESET}"
    _mstro_step "Detected Node $_mstro_node_v"
}

# Pick a package manager. User override wins. Otherwise prefer bun > pnpm > npm
# matching real-world adoption among devs likely to install global CLIs.
_mstro_detect_pm() {
    if [ -n "${MSTRO_INSTALL_PM:-}" ]; then
        case "$MSTRO_INSTALL_PM" in
            bun|pnpm|npm) MSTRO_PM="$MSTRO_INSTALL_PM" ;;
            *)
                _mstro_fail \
                    "Unknown MSTRO_INSTALL_PM: $MSTRO_INSTALL_PM" \
                    "Set MSTRO_INSTALL_PM to one of: bun, pnpm, npm."
                ;;
        esac
        if ! command -v "$MSTRO_PM" >/dev/null 2>&1; then
            _mstro_fail \
                "Requested package manager not found: $MSTRO_PM" \
                "Install $MSTRO_PM or unset MSTRO_INSTALL_PM."
        fi
    elif command -v bun >/dev/null 2>&1; then
        MSTRO_PM="bun"
    elif command -v pnpm >/dev/null 2>&1; then
        MSTRO_PM="pnpm"
    elif command -v npm >/dev/null 2>&1; then
        MSTRO_PM="npm"
    else
        _mstro_fail \
            "Could not find npm, pnpm, or bun on PATH." \
            "Install one (https://nodejs.org includes npm), then re-run this script."
    fi

    _mstro_step "Detected package manager: $MSTRO_PM"
}

# Only npm has a real "system prefix needs sudo" failure mode. bun/pnpm install
# globals into user-owned dirs (~/.bun/bin, ~/.local/share/pnpm). We refuse to
# auto-sudo and explain the fix instead.
_mstro_check_writable_prefix() {
    [ "$MSTRO_PM" = "npm" ] || return 0

    _mstro_prefix="$(npm config get prefix 2>/dev/null || printf '')"
    [ -n "$_mstro_prefix" ] || return 0

    if [ ! -w "$_mstro_prefix" ] && [ ! -w "$_mstro_prefix/lib" ] 2>/dev/null; then
        printf '\n'
        _mstro_fail \
            "npm's global prefix is not writable: $_mstro_prefix" \
            "$(printf '%s\n' \
                "Mstro will not auto-elevate. Pick one of:" \
                "  - Use a Node version manager (recommended): https://github.com/nvm-sh/nvm" \
                "  - Set a user-local npm prefix: npm config set prefix ~/.local" \
                "  - Or re-run this command yourself with sudo:" \
                "      sudo npm install -g $MSTRO_PKG_NAME")"
    fi
}

_mstro_run() {
    if [ "${MSTRO_INSTALL_DRY_RUN:-0}" = "1" ]; then
        printf '%s[dry-run]%s %s\n' "$MSTRO_DIM" "$MSTRO_RESET" "$*"
        return 0
    fi
    "$@"
}

_mstro_install_pkg() {
    _mstro_step "Installing $MSTRO_PKG_NAME globally..."

    case "$MSTRO_PM" in
        bun)  _mstro_run bun  add  -g "$MSTRO_PKG_NAME@$MSTRO_VERSION" ;;
        pnpm) _mstro_run pnpm add  -g "$MSTRO_PKG_NAME@$MSTRO_VERSION" ;;
        npm)  _mstro_run npm  install -g "$MSTRO_PKG_NAME@$MSTRO_VERSION" ;;
    esac
}

# After install the bin dir may not be on the current shell's PATH (bun/pnpm
# globals are in non-default locations). Probe the canonical bin dir per pm,
# fall back to PATH lookup, and surface the resolved binary so the user knows
# where it landed.
_mstro_verify() {
    [ "${MSTRO_INSTALL_DRY_RUN:-0}" = "1" ] && return 0

    _mstro_bin=""
    case "$MSTRO_PM" in
        bun)
            _mstro_dir="$(bun pm bin -g 2>/dev/null || printf '')"
            [ -n "$_mstro_dir" ] && [ -x "$_mstro_dir/$MSTRO_BIN_NAME" ] && _mstro_bin="$_mstro_dir/$MSTRO_BIN_NAME"
            ;;
        pnpm)
            _mstro_dir="$(pnpm bin -g 2>/dev/null || printf '')"
            [ -n "$_mstro_dir" ] && [ -x "$_mstro_dir/$MSTRO_BIN_NAME" ] && _mstro_bin="$_mstro_dir/$MSTRO_BIN_NAME"
            ;;
        npm)
            _mstro_dir="$(npm bin -g 2>/dev/null || npm config get prefix 2>/dev/null)"
            [ -n "$_mstro_dir" ] && [ -x "$_mstro_dir/$MSTRO_BIN_NAME" ] && _mstro_bin="$_mstro_dir/$MSTRO_BIN_NAME"
            [ -z "$_mstro_bin" ] && [ -n "$_mstro_dir" ] && [ -x "$_mstro_dir/bin/$MSTRO_BIN_NAME" ] && _mstro_bin="$_mstro_dir/bin/$MSTRO_BIN_NAME"
            ;;
    esac

    if [ -z "$_mstro_bin" ]; then
        _mstro_bin="$(command -v "$MSTRO_BIN_NAME" 2>/dev/null || printf '')"
    fi

    if [ -z "$_mstro_bin" ]; then
        _mstro_fail \
            "Install completed but '$MSTRO_BIN_NAME' is not on your PATH." \
            "Open a new shell and try again, or add your $MSTRO_PM global bin dir to PATH."
    fi

    _mstro_installed_v="$("$_mstro_bin" --version 2>/dev/null || printf 'unknown')"
    _mstro_installed_v_short="$(printf '%s' "$_mstro_installed_v" | head -n1)"
    _mstro_step "Installed $MSTRO_BIN_NAME $_mstro_installed_v_short"
}

_mstro_print_done() {
    [ "${MSTRO_INSTALL_DRY_RUN:-0}" = "1" ] && {
        printf '\n'
        _mstro_info "Dry run complete."
        return 0
    }

    _mstro_path_hint=""
    if [ -n "${_mstro_bin:-}" ]; then
        _mstro_bin_dir="$(dirname "$_mstro_bin")"
        case ":${PATH}:" in
            *":$_mstro_bin_dir:"*) ;;
            *) _mstro_path_hint="$_mstro_bin_dir" ;;
        esac
    fi

    printf '\n'
    if [ -n "$_mstro_path_hint" ]; then
        _mstro_info "${MSTRO_BOLD}First, add to PATH:${MSTRO_RESET}"
        _mstro_step "  export PATH=\"$_mstro_path_hint:\$PATH\""
        printf '\n'
    fi
    _mstro_info "To connect a project, cd into it and run:"
    printf '\n'
    _mstro_info "    ${MSTRO_BOLD}mstro${MSTRO_RESET}"
    printf '\n'
    _mstro_info "Then open ${MSTRO_BOLD}https://app.mstro.app${MSTRO_RESET} to start working."
    printf '\n'
    _mstro_step "Re-run this installer to upgrade. Uninstall: $MSTRO_PM uninstall -g $MSTRO_PKG_NAME"
}

_mstro_install "$@"
