#!/bin/sh
set -eu

# boxd CLI installer
# Usage: curl -fsSL https://boxd.sh/downloads/cli/install.sh | sh

BASE_URL="https://boxd.sh/downloads/cli"
# Installed binary name. scripts/deploy/cli-installer.sh rewrites this to
# `boxd-stg` / `boxd-dev` for the non-prod installers (anchored sed), so a
# staging `boxd-stg` can be installed alongside the prod `boxd` without
# overwriting it.
BINARY_NAME="boxd"
INSTALL_DIR="$HOME/.local/bin"
BINARY_PATH="$INSTALL_DIR/$BINARY_NAME"

# --- Brand banner ---------------------------------------------------------
# The block-letter "boxd" wordmark from the original CLI's post-link welcome
# screen, printed once at the top of the install in the terminal's own
# foreground (white on a dark theme, black on a light one).
#
# On an interactive terminal the letters settle in over ~0.6s: each cell
# starts as noise (░ ▒ ▓) and resolves to a solid █, left to right with a
# little randomness, then the final frame is the plain wordmark. The frames
# are computed at run time by awk (already a dependency below) from the
# ASCII mask, so the script carries one copy of the art, not fifteen. Piped
# or redirected output (logs, CI, `| tee`) gets the static wordmark with no
# escape codes at all. Fractional `sleep` isn't POSIX; where it is refused
# the animation is skipped rather than played with whole-second pauses.
print_boxd_banner() {
    __M1='######   ######  ##   ## ######'
    __M2='##   ## ##    ##  ## ##  ##   ##'
    __M3='######  ##    ##   ###   ##   ##'
    __M4='##   ## ##    ##  ## ##  ##   ##'
    __M5='######   ######  ##   ## ######'
    printf '\n'
    if [ -t 1 ] && [ "${TERM:-}" != "dumb" ] && sleep 0.01 2>/dev/null; then
        # Hide the cursor for the redraws; put it back even on Ctrl-C.
        printf '\033[?25l'
        trap 'printf "\033[?25h"; exit 130' INT TERM
        awk -v m1="$__M1" -v m2="$__M2" -v m3="$__M3" -v m4="$__M4" -v m5="$__M5" '
        BEGIN {
            m[1] = m1; m[2] = m2; m[3] = m3; m[4] = m4; m[5] = m5
            sh[1] = "░"; sh[2] = "▒"; sh[3] = "▓"; sh[4] = "█"
            W = 0
            for (y = 1; y <= 5; y++) if (length(m[y]) > W) W = length(m[y])
            # Each lit cell gets its own start time: mostly its column (so
            # the mark sweeps in from the left), plus noise so it shimmers.
            srand(7)
            for (y = 1; y <= 5; y++)
                for (x = 1; x <= W; x++)
                    t[y, x] = 0.45 * (x - 1) / W + 0.55 * rand()
            F = 14
            for (f = 0; f <= F; f++) {
                p = 1.55 * f / F
                for (y = 1; y <= 5; y++) {
                    line = "   "
                    for (x = 1; x <= W; x++) {
                        if (substr(m[y], x, 1) != "#") { line = line " "; continue }
                        a = p - t[y, x]
                        if (a < 0)         line = line " "
                        else if (a < 0.15) line = line sh[1]
                        else if (a < 0.30) line = line sh[2]
                        else if (a < 0.45) line = line sh[3]
                        else               line = line sh[4]
                    }
                    printf "%s\n", line
                }
                if (f < F) { fflush(); system("sleep 0.045"); printf "\033[5A" }
            }
        }'
        printf '\033[?25h'
        trap - INT TERM
    else
        printf '%s\n' "$__M1" "$__M2" "$__M3" "$__M4" "$__M5" | sed 's/#/█/g; s/^/   /'
    fi
}

# --- Platform detection ---

OS=$(uname -s)
ARCH=$(uname -m)

case "${OS}" in
    Darwin)
        case "${ARCH}" in
            arm64) PLATFORM="darwin-arm64" ;;
            *)
                echo "error: unsupported architecture: ${ARCH} (only arm64 is supported on macOS)"
                exit 1
                ;;
        esac
        ;;
    Linux)
        case "${ARCH}" in
            x86_64) PLATFORM="linux-amd64" ;;
            aarch64) PLATFORM="linux-arm64" ;;
            *)
                echo "error: unsupported architecture: ${ARCH}"
                exit 1
                ;;
        esac
        ;;
    *)
        echo "error: unsupported OS: ${OS}"
        exit 1
        ;;
esac

print_boxd_banner

echo ""
echo "  ${BINARY_NAME} CLI installer (${PLATFORM})"
echo ""

# --- Fetch version manifest ---

# `install=1` marks this fetch as an installer run (nothing else reads the
# manifest, so the server counts it as a CLI install); `existing` says
# whether a binary was already at the install path (re-install / update)
# or not (fresh). Anonymous: no account is involved yet.
if [ -x "${BINARY_PATH}" ]; then EXISTING=1; else EXISTING=0; fi
MANIFEST_URL="${BASE_URL}/latest-${PLATFORM}.json?install=1&existing=${EXISTING}"
MANIFEST=$(curl -fsSL "${MANIFEST_URL}" 2>/dev/null) || {
    echo "  x failed to fetch version info from ${MANIFEST_URL}"
    exit 1
}

BINARY_URL=$(echo "${MANIFEST}" | grep -o '"url" *: *"[^"]*"' | cut -d'"' -f4)
EXPECTED_SHA=$(echo "${MANIFEST}" | grep -o '"sha256" *: *"[^"]*"' | cut -d'"' -f4)
VERSION=$(echo "${MANIFEST}" | grep -o '"version" *: *"[^"]*"' | cut -d'"' -f4)

if [ -z "${BINARY_URL}" ] || [ -z "${EXPECTED_SHA}" ]; then
    echo "  x invalid version manifest"
    exit 1
fi

echo "  - version: v${VERSION}"

# --- Download binary ---

mkdir -p "${INSTALL_DIR}"

TMP_FILE=$(mktemp)
curl -fsSL "${BINARY_URL}" -o "${TMP_FILE}" || {
    rm -f "${TMP_FILE}"
    echo "  x failed to download binary"
    exit 1
}


# --- Verify hash ---

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

if [ "${ACTUAL_SHA}" != "${EXPECTED_SHA}" ]; then
    rm -f "${TMP_FILE}"
    echo "  x hash mismatch (expected ${EXPECTED_SHA}, got ${ACTUAL_SHA})"
    exit 1
fi


# --- Install ---

mv "${TMP_FILE}" "${BINARY_PATH}"
chmod +x "${BINARY_PATH}"

# Remove macOS quarantine attribute
if [ "${OS}" = "Darwin" ]; then
    xattr -d com.apple.quarantine "${BINARY_PATH}" 2>/dev/null || true
fi

echo "  - installed to ${BINARY_PATH}"

# --- Install Claude Code skills ---

SKILLS_ROOT="$HOME/.claude/skills"
mkdir -p "${SKILLS_ROOT}"

INSTALLED_SKILLS=""

# 1. boxd-cli skill — single SKILL.md file (the everyday CLI usage skill).
# Namespaced by the binary name so the staging installer (`boxd-stg-cli`)
# doesn't overwrite the prod `boxd-cli` skill when both are installed.
CLI_SKILL_DIR="${SKILLS_ROOT}/${BINARY_NAME}-cli"
mkdir -p "${CLI_SKILL_DIR}"
if curl -fsSL "${BASE_URL}/skill.md" -o "${CLI_SKILL_DIR}/SKILL.md" 2>/dev/null; then
    INSTALLED_SKILLS="${BINARY_NAME}-cli"
fi

# 2. boxd-setup-* skills — packaged as a tarball (each ships SKILL.md, some with
# an assets/ directory). The set evolves, so derive names from the tarball, and
# clean up skills retired/renamed in the current generation
# (e.g. boxd-setup-fix-on-issue → boxd-setup-fix) so a reinstall doesn't leave
# the old one lingering beside the new one.
SKILLS_TARBALL_URL="${BASE_URL}/skills.tar.gz"
TMP_TAR=$(mktemp)
if curl -fsSL "${SKILLS_TARBALL_URL}" -o "${TMP_TAR}" 2>/dev/null; then
    rm -rf "${SKILLS_ROOT}/boxd-setup-fix-on-issue"
    # The golden/preview/fix/deploy setup skills and boxd-migrate are retired
    # (hermes stays): remove installed copies and their agent-dir symlinks on
    # reinstall.
    for retired in boxd-setup-golden boxd-setup-deploy boxd-setup-fix boxd-setup-preview boxd-migrate; do
        rm -rf "${SKILLS_ROOT}/${retired}"
        rm -f "${CODEX_HOME:-$HOME/.codex}/skills/${retired}" \
              "$HOME/.config/opencode/skills/${retired}"
    done
    if tar -xzf "${TMP_TAR}" -C "${SKILLS_ROOT}" 2>/dev/null; then
        setup_names=$(tar -tzf "${TMP_TAR}" 2>/dev/null \
            | sed -n 's#^\(boxd-setup-[a-z-]*\)/.*#\1#p' | sort -u | tr '\n' ' ')
        INSTALLED_SKILLS="${INSTALLED_SKILLS} ${setup_names}"
    fi
fi
rm -f "${TMP_TAR}"

# One combined line, full skill names (boxd-cli first), comma-separated.
SKILLS_MSG=$(printf '%s' "${INSTALLED_SKILLS}" | tr ' ' '\n' | sed '/^$/d' \
    | tr '\n' ',' | sed 's/,$//; s/,/, /g')
if [ -n "${SKILLS_MSG}" ]; then
    echo "  - installed skills: ${SKILLS_MSG}"
else
    echo "  - skills skipped (optional)"
fi

# 3. Mirror the installed skills into the Codex and OpenCode skill dirs (when
# those agents are present) so all three coding agents discover them. Symlinks
# keep ~/.claude/skills/ the single source of truth — a reinstall/upgrade just
# re-points them. Codex follows symlinks and scans ${CODEX_HOME:-~/.codex}/skills;
# OpenCode scans ~/.config/opencode/skills.
if [ -n "${SKILLS_MSG}" ]; then
    link_skills_into() {
        # $1 = target skills dir. Returns non-zero only if the dir is uncreatable
        # (so the caller skips it); individual link failures are non-fatal.
        mkdir -p "$1" 2>/dev/null || return 1
        for name in ${INSTALLED_SKILLS}; do
            [ -n "${name}" ] || continue
            ln -sfn "${SKILLS_ROOT}/${name}" "$1/${name}" 2>/dev/null || true
        done
        return 0
    }
    wired=""
    if command -v codex >/dev/null 2>&1 \
        && link_skills_into "${CODEX_HOME:-$HOME/.codex}/skills"; then
        wired="${wired} codex"
    fi
    if command -v opencode >/dev/null 2>&1 \
        && link_skills_into "$HOME/.config/opencode/skills"; then
        wired="${wired} opencode"
    fi
    if [ -n "${wired}" ]; then
        echo "  - skills also wired for:${wired}"
    fi
fi

# --- PATH setup ---

add_to_path() {
    local shell_rc="$1"
    local shell_name="$2"
    local path_line='export PATH="$HOME/.local/bin:$PATH"'
    if [ -f "${shell_rc}" ]; then
        if ! grep -q '.local/bin' "${shell_rc}" 2>/dev/null; then
            echo "" >> "${shell_rc}"
            echo "# boxd CLI" >> "${shell_rc}"
            echo "${path_line}" >> "${shell_rc}"
            echo "  - added ~/.local/bin to PATH in ${shell_name}"
            return 0
        fi
    fi
    return 1
}

IN_PATH=false
case ":${PATH}:" in
    *":${INSTALL_DIR}:"*) IN_PATH=true ;;
esac

MODIFIED_RC=""
if [ "${IN_PATH}" = "false" ]; then
    if [ -n "${SHELL:-}" ]; then
        case "${SHELL}" in
            */zsh)  add_to_path "$HOME/.zshrc" "~/.zshrc" && MODIFIED_RC="zsh" ;;
            */bash) add_to_path "$HOME/.bashrc" "~/.bashrc" && MODIFIED_RC="bash" ;;
        esac
    fi
    if [ -z "${MODIFIED_RC}" ]; then
        add_to_path "$HOME/.zshrc" "~/.zshrc" 2>/dev/null && MODIFIED_RC="zsh"
        add_to_path "$HOME/.bashrc" "~/.bashrc" 2>/dev/null || true
    fi
fi

# --- Shell completions ---

# One-shot + idempotent: the CLI writes a fenced block into the shell rc
# (zsh/bash) or an autoloaded file (fish) that re-sources completions from
# the binary on shell startup. Best-effort — never fails the install.
COMPLETIONS_MSG=$("${BINARY_PATH}" completions --install 2>/dev/null) || COMPLETIONS_MSG=""
if [ -n "${COMPLETIONS_MSG}" ]; then
    echo "  - ${COMPLETIONS_MSG}"
fi

# Run a trivial command so the CLI's startup hook fires now: for an
# already-logged-in user this installs the background daemon (thin ssh-config
# sync) immediately instead of waiting for their first real command.
# (`completions --install` above is handled pre-runtime and doesn't trigger it.)
"${BINARY_PATH}" version >/dev/null 2>&1 || true

echo ""
# The one thing to do next, made to stand out: a green check, and the
# command alone on its own line so a triple-click selects exactly it
# (terminals have no click-to-copy; selecting is copying on most). On a
# terminal the command is bold, in the CLI's own link colour; piped or
# NO_COLOR output keeps the plain text. An update (a binary was already
# installed) keeps the existing login, so it gets no sign-in prompt.
if [ -t 1 ] && [ "${TERM:-}" != "dumb" ] && [ -z "${NO_COLOR:-}" ]; then
    COLOR=1
else
    COLOR=0
fi
if [ "${EXISTING}" = "1" ]; then
    if [ "${COLOR}" = "1" ]; then
        printf '  \033[32m\342\234\223\033[0m Updated to v%s\n' "${VERSION}"
    else
        echo "  Updated to v${VERSION}"
    fi
elif [ "${COLOR}" = "1" ]; then
    printf '  \033[32m\342\234\223\033[0m Ready! Sign in to get started:\n\n'
    printf '      \033[1;38;2;122;162;247m%s auth login\033[0m\n' "${BINARY_NAME}"
else
    echo "  Ready! Sign in to get started:"
    echo ""
    echo "      ${BINARY_NAME} auth login"
fi

if [ "${IN_PATH}" = "false" ]; then
    echo ""
    echo "  Open a new terminal, or reload your shell:"
    if [ "${MODIFIED_RC}" = "zsh" ]; then
        echo "    source ~/.zshrc"
    elif [ "${MODIFIED_RC}" = "bash" ]; then
        echo "    source ~/.bashrc"
    else
        echo "    export PATH=\"\$HOME/.local/bin:\$PATH\""
    fi
fi
echo ""
