#!/bin/sh
set -e

OAK_URL="${OAK_URL:-https://oak.space}"

# --- pretty output -------------------------------------------------------
# A little terminal polish: a green braille spinner on the slow steps, a ✓/✗
# on completion, and an oak-green banner. This is purely cosmetic and is
# gated on supports_fancy(), so anything non-interactive — CI logs,
# `curl ... | sh > file`, NO_COLOR, or a dumb terminal — falls back to plain,
# greppable lines with no escape codes.
supports_fancy() {
    [ -t 1 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-}" != "dumb" ]
}

if supports_fancy; then
    C_RESET=$(printf '\033[0m')
    C_DIM=$(printf '\033[2m')
    C_BOLD=$(printf '\033[1m')
    C_GREEN=$(printf '\033[38;5;107m')   # oak moss green
    C_GREENB=$(printf '\033[38;5;113m')  # brighter green for the ✓
    C_RED=$(printf '\033[38;5;203m')
else
    C_RESET= C_DIM= C_BOLD= C_GREEN= C_GREENB= C_RED=
fi

SPIN_PID=
SPIN_MSG=

# Kill the background animator if one is running (internal helper, also used
# by the cleanup trap). Guarded so a dead PID never trips `set -e`.
_spin_kill() {
    if [ -n "$SPIN_PID" ]; then
        kill "$SPIN_PID" 2>/dev/null || true
        wait "$SPIN_PID" 2>/dev/null || true
        SPIN_PID=
    fi
}

# spin_start "Doing the thing" — begin an animated step. The real work runs in
# the foreground after this returns; call spin_done/spin_fail to finish it.
spin_start() {
    SPIN_MSG="$1"
    if ! supports_fancy; then
        printf '  %s ...\n' "$SPIN_MSG"
        return
    fi
    printf '\033[?25l'  # hide cursor for the duration of the spin
    # Animate braille frames in oak green at ~12fps until killed. Runs in a
    # subshell so the foreground keeps doing the actual download/verify work.
    (
        frames='⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏'
        while : ; do
            for f in $frames; do
                printf '\r%s%s%s %s' "$C_GREEN" "$f" "$C_RESET" "$SPIN_MSG"
                sleep 0.08
            done
        done
    ) &
    SPIN_PID=$!
}

# spin_done ["final message"] — stop the animator, leave a green ✓ line.
spin_done() {
    final=${1:-$SPIN_MSG}
    if ! supports_fancy; then
        [ -n "${1:-}" ] && printf '  %s\n' "$final"
        return
    fi
    _spin_kill
    printf '\r\033[K%s✓%s %s\n' "$C_GREENB" "$C_RESET" "$final"
    printf '\033[?25h'  # restore cursor
}

# spin_fail ["final message"] — stop the animator, leave a red ✗ line.
spin_fail() {
    final=${1:-$SPIN_MSG}
    if ! supports_fancy; then
        printf '  %s\n' "$final"
        return
    fi
    _spin_kill
    printf '\r\033[K%s✗%s %s\n' "$C_RED" "$C_RESET" "$final"
    printf '\033[?25h'
}

# An instant fact (no spinner) — a dim bullet line.
note() {
    if supports_fancy; then
        printf '%s•%s %s\n' "$C_GREEN" "$C_RESET" "$1"
    else
        printf '  %s\n' "$1"
    fi
}

# Fatal error → red ✗ on stderr, then exit.
die() {
    _spin_kill
    if supports_fancy; then
        printf '%s✗%s %s\n' "$C_RED" "$C_RESET" "$1" >&2
        printf '\033[?25h' >&2
    else
        printf 'Error: %s\n' "$1" >&2
    fi
    exit 1
}

# Always tidy up: stop any spinner, restore the cursor, remove the temp dir,
# and preserve the real exit code (so failures still surface as failures).
TMP_DIR=
cleanup() {
    _rc=$?
    _spin_kill
    if supports_fancy; then printf '\033[?25h'; fi
    if [ -n "$TMP_DIR" ]; then rm -rf "$TMP_DIR"; fi
    exit "$_rc"
}
trap cleanup EXIT INT TERM

# --- banner --------------------------------------------------------------
printf '\n%s↟ Oak%s  %sthe agentic substrate%s\n\n' \
    "$C_GREEN$C_BOLD" "$C_RESET" "$C_DIM" "$C_RESET"

# darwin-arm64 covers Apple Silicon (and Intel Macs via Rosetta 2).
# linux-x86_64 covers CI runners and Linux servers.
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
case "$OS" in
    darwin)
        PLATFORM="darwin-arm64"
        ;;
    linux)
        case "$ARCH" in
            x86_64|amd64)
                PLATFORM="linux-x86_64"
                ;;
            *)
                die "Unsupported Linux architecture: $ARCH (only x86_64 is supported)"
                ;;
        esac
        ;;
    *)
        die "Unsupported operating system: $OS (macOS and Linux are supported)"
        ;;
esac
note "platform: $PLATFORM"

# Get latest version
spin_start "Fetching latest version"
if VERSION=$(curl -fsSL "$OAK_URL/api/releases/latest" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4) && [ -n "$VERSION" ]; then
    spin_done "Latest version: $VERSION"
else
    spin_fail "Could not determine latest version"
    exit 1
fi

# Create temp directory (cleaned up by the EXIT trap above)
TMP_DIR=$(mktemp -d)

# Download binary
spin_start "Downloading oak $VERSION ($PLATFORM)"
if curl -fsSL "$OAK_URL/api/releases/$VERSION/$PLATFORM" -o "$TMP_DIR/oak"; then
    spin_done "Downloaded oak $VERSION"
else
    spin_fail "Download failed"
    exit 1
fi

# Download + verify checksum
spin_start "Verifying checksum"
if ! EXPECTED_SHA256=$(curl -fsSL "$OAK_URL/api/releases/$VERSION/$PLATFORM/sha256"); then
    spin_fail "Could not fetch checksum"
    exit 1
fi
if command -v sha256sum > /dev/null 2>&1; then
    ACTUAL_SHA256=$(sha256sum "$TMP_DIR/oak" | cut -d' ' -f1)
elif command -v shasum > /dev/null 2>&1; then
    ACTUAL_SHA256=$(shasum -a 256 "$TMP_DIR/oak" | cut -d' ' -f1)
else
    spin_done "Checksum skipped (no sha256sum/shasum available)"
    SKIP_VERIFY=1
fi

if [ -z "${SKIP_VERIFY:-}" ]; then
    if [ "$EXPECTED_SHA256" != "$ACTUAL_SHA256" ]; then
        spin_fail "Checksum verification failed"
        printf '    expected: %s\n    actual:   %s\n' "$EXPECTED_SHA256" "$ACTUAL_SHA256" >&2
        exit 1
    fi
    spin_done "Checksum verified"
fi

# Install binary
chmod +x "$TMP_DIR/oak"

# Honor INSTALL_DIR if set (e.g. CI runners want /usr/local/bin so the
# binary lands in a directory already on PATH). Falls back to the per-user
# default ~/.local/bin when running interactively.
INSTALL_DIR="${INSTALL_DIR:-${HOME}/.local/bin}"
mkdir -p "$INSTALL_DIR"
mv "$TMP_DIR/oak" "$INSTALL_DIR/oak"

printf '\n%s↟ Oak %s installed%s to %s%s%s\n\n' \
    "$C_GREEN$C_BOLD" "$VERSION" "$C_RESET" "$C_BOLD" "$INSTALL_DIR/oak" "$C_RESET"

# Check if install dir is in PATH
case ":$PATH:" in
    *":$INSTALL_DIR:"*)
        echo "Run 'oak --help' to get started."
        ;;
    *)
        echo "Add $INSTALL_DIR to your PATH:"
        echo ""
        echo "  export PATH=\"$INSTALL_DIR:\$PATH\""
        echo ""
        echo "Then run 'oak --help' to get started."
        ;;
esac

# On macOS, also install the "Oak Mount" app. macOS mounts via Apple FSKit
# (no kernel extension, nothing to brew install): the signed app registers the
# OakFS file-system extension that `oak mount` drives. It ships in the same
# release channel as the CLI under the `darwin-mounter` platform — the zip is a
# `ditto --keepParent`-packaged "Oak Mount.app". Best-effort: a release without
# the app (older, or built without the macOS signing secrets) just skips this,
# and `oak mount` will offer to install it on first use. Skipped on Linux.
if [ "$OS" = "darwin" ]; then
    echo ""
    spin_start "Installing the Oak Mount app (for 'oak mount')"
    MOUNTER_ZIP="$TMP_DIR/OakMount.zip"
    if curl -fsSL "$OAK_URL/api/releases/$VERSION/darwin-mounter" -o "$MOUNTER_ZIP" 2>/dev/null; then
        # Verify against the server-published checksum, same as the CLI binary.
        MOUNTER_EXPECTED=$(curl -fsSL "$OAK_URL/api/releases/$VERSION/darwin-mounter/sha256" 2>/dev/null || true)
        if command -v shasum > /dev/null 2>&1; then
            MOUNTER_ACTUAL=$(shasum -a 256 "$MOUNTER_ZIP" | cut -d' ' -f1)
        elif command -v sha256sum > /dev/null 2>&1; then
            MOUNTER_ACTUAL=$(sha256sum "$MOUNTER_ZIP" | cut -d' ' -f1)
        else
            MOUNTER_ACTUAL="$MOUNTER_EXPECTED"
        fi

        if [ -n "$MOUNTER_EXPECTED" ] && [ "$MOUNTER_EXPECTED" != "$MOUNTER_ACTUAL" ]; then
            spin_fail "Oak Mount checksum mismatch — skipping app install"
            printf '    expected: %s\n    actual:   %s\n' "$MOUNTER_EXPECTED" "$MOUNTER_ACTUAL" >&2
        else
            # Prefer /Applications; fall back to ~/Applications when it isn't
            # writable (a piped installer has no sudo).
            APP_DIR="/Applications"
            if [ ! -w "$APP_DIR" ]; then
                APP_DIR="$HOME/Applications"
                mkdir -p "$APP_DIR"
            fi
            rm -rf "$APP_DIR/Oak Mount.app"
            # `ditto -x -k` restores the bundle exactly (preserving the notarized
            # code signature); the zip was produced with `ditto -c -k --keepParent`,
            # so it unpacks to "$APP_DIR/Oak Mount.app". curl downloads carry no
            # Gatekeeper quarantine bit, so launching it is friction-free even
            # when the app is signed-but-not-notarized.
            if ditto -x -k "$MOUNTER_ZIP" "$APP_DIR" 2>/dev/null; then
                spin_done "Installed \"Oak Mount.app\" to $APP_DIR"
                # Launch once so macOS registers the OakFS extension with pluginkit.
                open "$APP_DIR/Oak Mount.app" 2>/dev/null || true
                echo ""
                echo "One more step (one time): enable the OakFS extension in"
                echo "  System Settings → General → Login Items & Extensions → File System Extensions"
                echo "(toggle OakFS on). Then 'oak mount' will work."
            else
                spin_fail "Could not unpack the Oak Mount app — 'oak mount' will offer to install it on first use"
            fi
        fi
    else
        spin_done "No Oak Mount app in this release yet ('oak mount' will offer to install it on first use)"
    fi
fi

# Offer to log in right away. This script is usually piped (curl ... | sh), so
# stdin is the script itself, not the keyboard — we read the answer from the
# controlling terminal (/dev/tty) and hand that same terminal to `oak login`,
# so its browser sign-in (and prompt-setup offer) stay interactive. Skipped
# automatically when there's no terminal (CI, cron) or when OAK_NO_LOGIN is set.
if [ -z "${OAK_NO_LOGIN:-}" ] && [ -t 1 ] && [ -r /dev/tty ]; then
    echo ""
    printf "Log in to %s now? [Y/n] " "$OAK_URL"
    if read -r OAK_LOGIN_ANSWER < /dev/tty; then
        case "$OAK_LOGIN_ANSWER" in
            ""|[Yy]|[Yy][Ee][Ss])
                # || true: a cancelled or failed login shouldn't make a
                # successful install look like it errored out.
                "$INSTALL_DIR/oak" login -r "$OAK_URL" < /dev/tty || true
                ;;
            *)
                echo "Skipped. Run 'oak login' when you're ready."
                ;;
        esac
    fi
fi
