#!/usr/bin/env sh
# SPDX-License-Identifier: BUSL-1.1
# Copyright (c) 2026 Eigentic, Inc. — see LICENSE.
# RookOne's one-line uv-tool installer: curl -LsSf https://get.rookone.io | sh

set -eu

ROOKONE_INDEX_URL="${ROOKONE_INDEX_URL:-https://packages.rookone.io/simple}"
ROOKONE_INSTALL_VERSION="${ROOKONE_INSTALL_VERSION:-}"
ROOKONE_INSTALL_EXTRAS="${ROOKONE_INSTALL_EXTRAS:-}"
_rk_original_path=$PATH
_rk_mcp_registered=0
_rk_pipx_loaded=0; _rk_pipx_available=0; _rk_pipx_list=""
_rk_path_activation_required=0; _rk_path_activation_dir=""

usage() {
    cat <<'EOF'
Install the RookOne client stack with uv tool.

Usage:
    sh install/get/install.sh [--version VERSION] [--extras LIST] [--no-statusline]
    curl -LsSf https://get.rookone.io | sh
    curl -LsSf https://get.rookone.io | sh -s -- --version VERSION
    curl -LsSf https://get.rookone.io | sh -s -- --extras dashboard,tui

Options:
    --version VERSION    Install one exact release (for recovery or rollback).
    --extras LIST        Also install human-facing extras: dashboard (browser
                         dashboard), tui (terminal UI). Extras already installed
                         are kept. Agent-only machines need neither.
    --force              Upgrade even if the new version cannot use some agents here.
    --no-statusline      Do not configure the Claude Code status line.
    -h, --help           Show this help without changing the machine.

The installer installs ONE uv tool, rookone, which provides the rookone CLI and
the rookone-channel MCP command from one environment. It migrates old pipx
copies and verifies every installed RookOne package at one version.
It preserves ~/.rookone and the configured secret vault.
EOF
}

parse_args() {
    while [ "$#" -gt 0 ]; do
        case "$1" in
            -h|--help)
                usage
                return 2
                ;;
            --version)
                if [ "$#" -lt 2 ] || [ -z "$2" ]; then
                    log_error "--version requires a value"
                    return 1
                fi
                case "$2" in
                    -*) log_error "--version requires a value"; return 1 ;;
                esac
                ROOKONE_INSTALL_VERSION=$2
                shift 2
                ;;
            --extras)
                if [ "$#" -lt 2 ] || [ -z "$2" ]; then
                    log_error "--extras requires a value"
                    return 1
                fi
                case "$2" in
                    -*) log_error "--extras requires a value"; return 1 ;;
                esac
                ROOKONE_INSTALL_EXTRAS=$2
                shift 2
                ;;
            --no-statusline)
                ROOKONE_NO_STATUSLINE=1
                shift
                ;;
            --force)
                ROOKONE_INSTALL_FORCE=1
                shift
                ;;
            *)
                log_error "unknown option: $1"
                log_error "Run with --help to see supported options."
                return 1
                ;;
        esac
    done

    for _rk_extra in $(printf '%s' "$ROOKONE_INSTALL_EXTRAS" | tr ',' ' '); do
        case "$_rk_extra" in
            dashboard|tui) ;;
            *)
                log_error "unknown extra: $_rk_extra (choose from: dashboard, tui)"
                return 1
                ;;
        esac
    done

    ROOKONE_INSTALL_VERSION=${ROOKONE_INSTALL_VERSION#v}
    if [ -n "$ROOKONE_INSTALL_VERSION" ] \
        && ! printf '%s\n' "$ROOKONE_INSTALL_VERSION" \
            | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([A-Za-z0-9.-]+)?$'; then
        log_error "invalid release version: $ROOKONE_INSTALL_VERSION"
        return 1
    fi
}

_rk_uv_tmp=""; _rk_preflight=""
_rk_cleanup() {
    if [ -n "$_rk_uv_tmp" ]; then
        rm -f "$_rk_uv_tmp"
    fi
    if [ -n "$_rk_preflight" ] && [ -d "$_rk_preflight" ]; then
        rm -r "$_rk_preflight"
    fi
}
trap _rk_cleanup EXIT
trap 'exit 129' HUP; trap 'exit 130' INT; trap 'exit 143' TERM

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'

log_info()    { printf '%b==>%b %s\n' "$BLUE"   "$NC" "$1" >&2; }
log_success() { printf '%b==>%b %s\n' "$GREEN"  "$NC" "$1" >&2; }
log_warning() { printf '%b==>%b %s\n' "$YELLOW" "$NC" "$1" >&2; }
log_error()   { printf '%bError:%b %s\n' "$RED"  "$NC" "$1" >&2; }

_rk_sha256() {
    if command -v sha256sum >/dev/null 2>&1; then
        sha256sum "$1" | awk '{print $1}'
    elif command -v shasum >/dev/null 2>&1; then
        shasum -a 256 "$1" | awk '{print $1}'
    else
        log_error "no sha256 tool found (need sha256sum or shasum)"
        return 1
    fi
}

_rk_verify_sha256() {
    _rk_f="$1"; _rk_want="$2"
    _rk_have=$(_rk_sha256 "$_rk_f") || return 1
    if [ "$_rk_have" != "$_rk_want" ]; then
        log_error "integrity check failed: expected $_rk_want, got $_rk_have"
        return 1
    fi
    return 0
}

_rk_require_https() {
    case "$1" in
        https://*) return 0 ;;
        http://localhost:*|http://localhost/*|http://127.0.0.1:*|http://127.0.0.1/*) return 0 ;;
        *) log_error "ROOKONE_INDEX_URL must use HTTPS (got: $(_rk_safe_url "$1"))"; return 1 ;;
    esac
}

_rk_validate_index_url() {
    case "$1" in
        *\?*|*\#*)
            log_error "ROOKONE_INDEX_URL must not include a query or fragment."
            return 1
            ;;
    esac
    _rk_require_https "$1"
}

_rk_safe_url() {
    _rk_url=$1
    case "$_rk_url" in
        *://*@*)
            _rk_scheme=${_rk_url%%://*}
            _rk_url="${_rk_scheme}://[redacted]@${_rk_url#*@}"
            ;;
    esac
    case "$_rk_url" in
        *\?*) printf '%s?[redacted]\n' "${_rk_url%%\?*}" ;;
        *) printf '%s\n' "$_rk_url" ;;
    esac
}

_rk_uv_resolve() {
    (
        unset UV_FIND_LINKS UV_INDEX UV_DEFAULT_INDEX UV_INDEX_URL
        unset UV_EXTRA_INDEX_URL UV_CONSTRAINT UV_OVERRIDE UV_BUILD_CONSTRAINT
        unset UV_NO_INDEX UV_INSECURE_HOST UV_CONFIG_FILE UV_PROJECT UV_WORKING_DIR
        unset UV_OFFLINE UV_EXCLUDE_NEWER UV_EXCLUDE UV_PRERELEASE UV_RESOLUTION
        unset UV_INDEX_STRATEGY UV_NO_CONFIG UV_NO_SOURCES
        uv "$@"
    )
}

# The rookone tool also exposes rookone-channel (one environment, two commands).
# `--with-executables-from` is the only way uv will link a dependency's script.
_rk_install_tool() {
    _rk_tool install "$@" --with-executables-from rookone-channel
}

# `uv tool install SPEC` or `uv tool run --from SPEC`, under one index policy.
_rk_tool() {
    _rk_mode=$1
    _rk_spec=$2
    shift 2
    _rk_from=""
    # --isolated: never the installed tool, which `--from rookone` alone would reuse.
    [ "$_rk_mode" = run ] && _rk_from="--isolated --from"
    # shellcheck disable=SC2086  # _rk_from is empty or two words
    _rk_uv_resolve tool "$_rk_mode" $_rk_from "$_rk_spec" \
        --index "$ROOKONE_INDEX_URL" \
        --index-strategy first-index \
        --no-config \
        --no-sources \
        --no-build-package sqlcipher3 \
        --no-build-package sqlcipher3-binary \
        "$@"
}

assert_supported_platform() {
    _rk_os=$(uname -s 2>/dev/null || printf unknown)
    _rk_arch=$(uname -m 2>/dev/null || printf unknown)
    case "${_rk_os}:${_rk_arch}" in
        Darwin:*)
            if ! command -v sqlcipher >/dev/null 2>&1; then
                log_error "SQLCipher is required for RookOne's local message database but was not found on PATH."
                log_error "Install SQLCipher first, then re-run the installer:"
                log_error "  brew install sqlcipher"
                log_error "Install is stopped before any local database can be created."
                exit 1
            fi
            ;;
    esac
}

ensure_uv() {
    if command -v uv >/dev/null 2>&1; then
        log_info "uv already installed ($(uv --version 2>/dev/null))"
        return
    fi

    log_info "uv not found — bootstrapping from astral.sh"

    if ! command -v curl >/dev/null 2>&1; then
        log_error "curl is required to bootstrap uv but was not found on PATH."
        log_error "Install curl first, then re-run the installer."
        exit 1
    fi

    _rk_uv_tmp=$(mktemp /tmp/uv-install.XXXXXX)

    UV_VERSION="0.11.19"
    UV_INSTALLER_SHA256="ef8cf0575d37cf3c72e05f153dd72a845a87a7bb9be86184d5fe931b8c426250"

    if ! curl -LsSf "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-installer.sh" -o "$_rk_uv_tmp"; then
        log_error "Failed to download the pinned uv installer (v${UV_VERSION})."
        exit 1
    fi

    if ! _rk_verify_sha256 "$_rk_uv_tmp" "$UV_INSTALLER_SHA256"; then
        log_error "uv installer integrity check failed — refusing to execute. Aborting."
        exit 1
    fi
    log_info "uv installer v${UV_VERSION} verified (sha256 ok)"

    sh "$_rk_uv_tmp" </dev/null
    rm -f "$_rk_uv_tmp"
    _rk_uv_tmp=""

    export PATH="$HOME/.local/bin:$PATH"

    if ! command -v uv >/dev/null 2>&1; then
        log_error "uv was installed but is not on PATH."
        log_error "Add ~/.local/bin to your PATH, then re-run the installer."
        exit 1
    fi

    log_success "uv installed ($(uv --version 2>/dev/null))"
}

detect_existing_rookone() {
    load_pipx_state || return 1
    if [ "$_rk_pipx_available" = "1" ]; then
        _rk_pipx_ver=$(printf '%s\n' "$_rk_pipx_list" | awk '$1=="rookone"{print $2; exit}')
        if [ -z "${_rk_pipx_ver:-}" ]; then
            _rk_pipx_ver=$(printf '%s\n' "$_rk_pipx_list" | awk '$1=="rookone-channel"{print $2; exit}')
        fi
        if [ -n "${_rk_pipx_ver:-}" ]; then
            printf 'pipx %s\n' "$_rk_pipx_ver"
            return 0
        fi
    fi

    if command -v uv >/dev/null 2>&1; then
        _rk_uv_ver=$(_rk_uv_resolve tool list 2>/dev/null | awk '$1=="rookone"{print $2; exit}')
        if [ -n "${_rk_uv_ver:-}" ]; then
            printf 'uv %s\n' "$_rk_uv_ver"
            return 0
        fi
    fi

    _rk_stale=$(command -v rookone 2>/dev/null || true)
    if [ -n "$_rk_stale" ]; then
        printf 'stale %s\n' "$_rk_stale"
        return 0
    fi

    printf 'fresh\n'
}

load_pipx_state() {
    [ "$_rk_pipx_loaded" = "0" ] || return 0
    if ! command -v pipx >/dev/null 2>&1; then
        _rk_pipx_loaded=1
        return 0
    fi
    if ! _rk_pipx_list=$(pipx list --short 2>/dev/null); then
        log_error "Could not verify pipx ownership; no installation was changed."
        return 1
    fi
    _rk_pipx_available=1
    _rk_pipx_loaded=1
}

print_identity_warning() {
    log_warning "Your identity spans ~/.rookone metadata and the configured secret vault."
    log_warning "Upgrades preserve both; use 'rookone deregister <agent>' for intentional removal."
    log_warning "Deleting only ~/.rookone can orphan vault entries and does not deregister the agent."
}

announce_install_plan() {
    _rk_case="$1"
    _rk_detail="${2:-}"
    case "$_rk_case" in
        fresh)
            log_info "No existing rookone found — installing fresh."
            ;;
        uv)
            log_info "Existing rookone ${_rk_detail} found (uv tool). Converging the client stack."
            print_identity_warning
            ;;
        pipx)
            log_warning "Existing rookone ${_rk_detail} found, installed via pipx."
            log_warning "Migrating to uv tool: proving the replacement before removing pipx…"
            print_identity_warning
            ;;
        stale)
            log_warning "Found an existing 'rookone' at ${_rk_detail} not managed by uv or pipx."
            log_warning "Installing a uv-managed copy. If the old one shadows it, remove it: rm ${_rk_detail}"
            print_identity_warning
            ;;
    esac
}

remove_legacy_pipx_tools() {
    load_pipx_state || return 1
    [ "$_rk_pipx_available" = "1" ] || return 0
    for _rk_package in rookone rookone-channel; do
        if printf '%s\n' "$_rk_pipx_list" | awk '{print $1}' | grep -qx "$_rk_package"; then
            log_info "Removing legacy pipx tool: $_rk_package"
            pipx uninstall "$_rk_package" >/dev/null 2>&1 || {
                log_error "Could not remove the legacy pipx $_rk_package tool."
                return 1
            }
        fi
    done
    if ! _rk_pipx_list=$(pipx list --short 2>/dev/null); then
        log_error "Could not verify pipx ownership after uninstall; refusing mixed ownership."
        return 1
    fi
    for _rk_package in rookone rookone-channel; do
        if printf '%s\n' "$_rk_pipx_list" | awk '{print $1}' | grep -qx "$_rk_package"; then
            log_error "pipx still reports $_rk_package after uninstall; refusing mixed ownership."
            return 1
        fi
    done
}

# Extras recorded in the installed tool's uv receipt, so an upgrade keeps them.
installed_extras() {
    _rk_receipt_dir=$(_rk_uv_resolve tool dir 2>/dev/null) || return 0
    _rk_receipt="${_rk_receipt_dir%/}/rookone/uv-receipt.toml"
    [ -r "$_rk_receipt" ] || return 0
    sed -n 's/.*name = "rookone", extras = \[\([^]]*\)\].*/\1/p' "$_rk_receipt" \
        | head -n 1 | tr -d ' "'
}

# rookone==X + requested and already-installed extras -> rookone[a,b]==X
with_extras() {
    _rk_wanted=$(printf '%s,%s\n' "$ROOKONE_INSTALL_EXTRAS" "$(installed_extras)" \
        | tr ',' '\n' | sed '/^$/d' | sort -u | paste -sd, -)
    if [ -z "$_rk_wanted" ]; then
        printf '%s\n' "$1"
    else
        printf 'rookone[%s]%s\n' "$_rk_wanted" "${1#rookone}"
    fi
}

stage_pipx_migration() {
    load_pipx_state || return 1
    [ "$_rk_pipx_available" = "1" ] || return 0
    printf '%s\n' "$_rk_pipx_list" | awk '{print $1}' \
        | grep -Eq '^(rookone|rookone-channel)$' || return 0

    _rk_preflight=$(mktemp -d /tmp/rookone-install-preflight.XXXXXX)
    if ! (
        UV_TOOL_DIR="$_rk_preflight/tools"
        UV_TOOL_BIN_DIR="$_rk_preflight/bin"
        export UV_TOOL_DIR UV_TOOL_BIN_DIR
        _rk_install_tool "$_rk_base_spec"
    ) >/dev/null 2>&1; then
        rm -r "$_rk_preflight"
        _rk_preflight=""
        log_error "The requested RookOne release is not installable from the configured index."
        log_error "The existing pipx tools were preserved; check the index and retry."
        return 1
    fi
    _rk_staged_version=$(UV_TOOL_DIR="$_rk_preflight/tools" _rk_uv_resolve tool list 2>/dev/null \
        | awk '$1 == "rookone" {sub(/^v/, "", $2); print $2; exit}')
    if [ -z "$_rk_staged_version" ]; then
        rm -r "$_rk_preflight"
        _rk_preflight=""
        log_error "The staged RookOne tool did not report a version; pipx was preserved."
        return 1
    fi
    _rk_base_spec="rookone==$_rk_staged_version"
    _rk_rookone_spec=$(with_extras "$_rk_base_spec")
    rm -r "$_rk_preflight"
    _rk_preflight=""
}

pipx_owns_entry() {
    _rk_command=$1; _rk_owner=$2; _rk_entry=$3
    [ "$_rk_pipx_available" = "1" ] || return 1
    printf '%s\n' "$_rk_pipx_list" | awk '{print $1}' | grep -qx "$_rk_owner" || return 1
    [ -L "$_rk_entry" ] || return 1
    _rk_pipx_home=$(pipx environment --value PIPX_HOME 2>/dev/null) || return 1
    _rk_pipx_bin=$(pipx environment --value PIPX_BIN_DIR 2>/dev/null) || return 1
    _rk_pipx_target=$(readlink "$_rk_entry") || return 1
    [ "$_rk_entry" = "${_rk_pipx_bin%/}/$_rk_command" ] \
        && [ "$_rk_pipx_target" = "${_rk_pipx_home%/}/venvs/$_rk_owner/bin/$_rk_command" ]
}

uv_owns_entry() {
    _rk_command=$1; _rk_owner=$2; _rk_entry=$3; _rk_uv_tools=$4
    printf '%s\n' "$_rk_uv_tools" | awk '{print $1}' | grep -qx "$_rk_owner" || return 1
    [ -L "$_rk_entry" ] || return 1
    _rk_uv_tool_dir=$(_rk_uv_resolve tool dir 2>/dev/null) || return 1
    [ -n "$_rk_uv_tool_dir" ] || return 1
    _rk_uv_target=$(readlink "$_rk_entry") || return 1
    case "$_rk_uv_target" in
        /*) ;;
        *) _rk_uv_target="${_rk_entry%/*}/$_rk_uv_target" ;;
    esac
    _rk_uv_target_name=${_rk_uv_target##*/}
    _rk_uv_target_parent=${_rk_uv_target%/*}
    _rk_uv_expected_parent="${_rk_uv_tool_dir%/}/$_rk_owner/bin"
    [ "$_rk_uv_target_parent" != "$_rk_uv_target" ] || return 1
    _rk_uv_target_parent=$(CDPATH='' cd -P "$_rk_uv_target_parent" 2>/dev/null && pwd -P) \
        || return 1
    _rk_uv_expected_parent=$(CDPATH='' cd -P "$_rk_uv_expected_parent" 2>/dev/null && pwd -P) \
        || return 1
    [ "$_rk_uv_target_parent/$_rk_uv_target_name" \
        = "$_rk_uv_expected_parent/$_rk_command" ]
}

guard_uv_bin_entry() {
    _rk_command=$1; _rk_owner=$2; _rk_pipx_owner=${3:-$2}
    if ! _rk_tool_bin=$(_rk_uv_resolve tool dir --bin 2>/dev/null) \
        || [ -z "$_rk_tool_bin" ]; then
        log_error "Could not locate the uv tool bin directory; no command was changed."
        return 1
    fi
    _rk_entry="$_rk_tool_bin/$_rk_command"
    [ ! -e "$_rk_entry" ] && [ ! -L "$_rk_entry" ] && return 0
    if ! _rk_uv_tools=$(_rk_uv_resolve tool list 2>/dev/null); then
        log_error "Could not verify uv ownership of $_rk_entry; it was preserved."
        return 1
    fi
    uv_owns_entry "$_rk_command" "$_rk_owner" "$_rk_entry" "$_rk_uv_tools" && return 0
    pipx_owns_entry "$_rk_command" "$_rk_pipx_owner" "$_rk_entry" && return 0
    if [ "$_rk_command" = rookone-channel ] \
        && uv_owns_entry "$_rk_command" rookone-channel "$_rk_entry" "$_rk_uv_tools"; then
        log_error "rookone-channel is still installed as its own uv tool ($_rk_entry)."
        log_error "RookOne is now one tool. Run: uv tool uninstall rookone-channel"
        log_error "Then re-run the installer. Nothing was changed."
        return 1
    fi
    log_error "Unmanaged command exists at $_rk_entry; it was preserved."
    log_error "Move it aside, then re-run the installer."
    return 1
}

guard_install_entries() {
    for _rk_command in \
        rookone rookone-mirror rookone-codex-hook rookone-codex-collector; do
        guard_uv_bin_entry "$_rk_command" rookone || return 1
    done
    # pipx kept the channel as its own tool, which the migration replaces.
    guard_uv_bin_entry rookone-channel rookone rookone-channel || return 1
}

pid_file_is_live() {
    [ -r "$1" ] || return 1
    _rk_pid=""
    IFS= read -r _rk_pid < "$1" || [ -n "$_rk_pid" ] || return 1
    case "$_rk_pid" in
        ''|*[!0-9]*) return 1 ;;
    esac
    [ "$_rk_pid" -gt 1 ] && kill -0 "$_rk_pid" 2>/dev/null
}

local_stack_is_running() {
    _rk_home=${ROOKONE_HOME:-"$HOME/.rookone"}
    pid_file_is_live "$_rk_home/server.pid" \
        || pid_file_is_live "$_rk_home/leaf/nats-server.pid" \
        || pid_file_is_live "$_rk_home/inbox-mirror.pid"
}

install_rookone() {
    _rk_validate_index_url "$ROOKONE_INDEX_URL" || return 1
    _rk_plan=$(detect_existing_rookone) || return 1
    _rk_case=${_rk_plan%% *}
    _rk_detail=${_rk_plan#* }
    [ "$_rk_detail" = "$_rk_plan" ] && _rk_detail=""

    announce_install_plan "$_rk_case" "$_rk_detail"
    _rk_stack_was_running=0
    if local_stack_is_running; then
        _rk_stack_was_running=1
    fi

    _rk_base_spec=rookone
    if [ -n "$ROOKONE_INSTALL_VERSION" ]; then
        _rk_base_spec="rookone==${ROOKONE_INSTALL_VERSION}"
    fi
    _rk_rookone_spec=$(with_extras "$_rk_base_spec")
    guard_install_entries || return 1
    if [ "$_rk_case" != "fresh" ]; then
        # The check needs the bare release; extras would only download more.
        check_agents_before_upgrade "$_rk_base_spec" || return 1
    fi
    stage_pipx_migration || return 1
    remove_legacy_pipx_tools || return 1
    guard_install_entries || return 1

    log_info "Installing ${_rk_rookone_spec} from $(_rk_safe_url "$ROOKONE_INDEX_URL")"

    # first-index prevents dependency confusion; --force converges old owners.
    _rk_force=""
    case "$_rk_case" in uv|pipx|stale) _rk_force="--force" ;; esac

    # SQLCipher must be a published wheel, never a machine-specific source build.
    # shellcheck disable=SC2086  # _rk_force is intentionally word-split (empty or --force)
    if ! _rk_install_tool "$_rk_rookone_spec" $_rk_force; then
        log_error "Could not install $_rk_rookone_spec; no post-install setup was changed."
        return 1
    fi

    _rk_tool_bin=$(_rk_uv_resolve tool dir --bin 2>/dev/null || printf '%s/.local/bin' "$HOME")
    export PATH="${_rk_tool_bin}:$PATH"

    if [ "$_rk_case" != "fresh" ] || [ "$_rk_stack_was_running" = "1" ]; then
        _rk_candidate="${_rk_tool_bin}/rookone"
        if [ ! -x "$_rk_candidate" ]; then
            log_error "Installed rookone candidate was not found at ${_rk_candidate}."
            return 1
        fi
        log_info "Stopping the existing local stack with the installed candidate"
        if ! "$_rk_candidate" stop; then
            log_error "The installed candidate could not prove a complete shutdown."
            log_error "Post-install setup was aborted; lifecycle evidence was retained."
            return 1
        fi
        if [ "$_rk_stack_was_running" = "1" ]; then
            log_info "Restarting the local stack with the installed version"
            if ! "$_rk_candidate" start --background; then
                log_warning "The cloud bridge could not be restored; recovering local messaging."
                if ! "$_rk_candidate" stop; then
                    log_error "The failed bridge start could not be stopped safely."
                    log_error "Resolve it before retrying the installation."
                    return 1
                fi
                if ! "$_rk_candidate" start --local-only --background; then
                    log_error "The installed candidate could not restore either local stack mode."
                    log_error "Recover with: rookone start --local-only --background"
                    return 1
                fi
                log_warning "Local messaging is running, but cross-machine delivery is unavailable."
                log_warning "After fixing credentials, run: rookone start --as NAME --background"
            fi
        fi
    fi
}

# Ask the version being installed which agents here it cannot use (exit 3 when
# one would be cut off with no replacement). Its own rule, not this script's,
# so a later release that stops loading something says so here unchanged.
check_agents_before_upgrade() {
    _rk_check=0
    _rk_tool run "$1" rookone upgrade --check || _rk_check=$?
    case "$_rk_check" in
        0) return 0 ;;
        3)
            if [ "${ROOKONE_INSTALL_FORCE:-0}" = "1" ]; then
                log_warning "Upgrading anyway (--force): the agents above stop working."
                return 0
            fi
            log_error "Upgrade stopped: the new version cannot use the agents above."
            log_error "Nothing was changed. To upgrade anyway, re-run with --force."
            return 1
            ;;
        *)
            log_warning "Could not check this machine's agents against $1; continuing."
            return 0
            ;;
    esac
}

installed_rookone_version() {
    _rk_uv_resolve tool list 2>/dev/null | awk '
        $1 == "rookone" {
            version = $2
            sub(/^v/, "", version)
            print version
            exit
        }
    '
}

verify_installation() {
    _rk_expected=$(installed_rookone_version)
    [ -n "$_rk_expected" ] || { log_error "uv does not report an installed rookone tool."; return 1; }
    if [ -n "$ROOKONE_INSTALL_VERSION" ] && [ "$_rk_expected" != "$ROOKONE_INSTALL_VERSION" ]; then
        log_error "uv installed rookone $_rk_expected, expected $ROOKONE_INSTALL_VERSION."
        return 1
    fi

    _rk_tool_bin=$(_rk_uv_resolve tool dir --bin 2>/dev/null || true)
    [ -n "$_rk_tool_bin" ] || { log_error "Could not locate the uv tool bin directory."; return 1; }
    export PATH="${_rk_tool_bin}:$PATH"
    _rk_tool_root=$(_rk_uv_resolve tool dir 2>/dev/null || true)
    [ -n "$_rk_tool_root" ] || { log_error "Could not locate uv tool environments."; return 1; }

    for _rk_command in rookone rookone-channel; do
        _rk_active=$(command -v "$_rk_command" 2>/dev/null || true)
        if [ "$_rk_active" != "${_rk_tool_bin}/${_rk_command}" ]; then
            log_error "Active $_rk_command is ${_rk_active:-missing}, not the uv-owned command."
            return 1
        fi
        _rk_reported=$("$_rk_active" --version 2>/dev/null | awk '{print $NF; exit}')
        if [ "${_rk_reported#v}" != "$_rk_expected" ]; then
            log_error "Active $_rk_command reports ${_rk_reported:-no version}, expected $_rk_expected."
            return 1
        fi
        verify_invoking_path "$_rk_command" "${_rk_tool_bin}/${_rk_command}" || return 1
    done
    "$_rk_tool_root/rookone/bin/python" -c \
        'import importlib.metadata as m,sys; e=sys.argv[1]; bad=[f"{n}={m.version(n)}" for n in sys.argv[2:] if m.version(n)!=e]; raise SystemExit("version mismatch: "+", ".join(bad) if bad else 0)' \
        "$_rk_expected" rookone rookone-channel rookone-core rookone-sdk rookone-shared || return 1
    log_success "verified uv-owned $_rk_expected lockstep across all five RookOne packages in one environment"
}

verify_invoking_path() {
    _rk_name=$1
    _rk_expected_path=$2
    _rk_parent_active=$(
        PATH=$_rk_original_path
        export PATH
        command -v "$_rk_name" 2>/dev/null || true
    )
    # Compare resolved directories, not spellings: uv under XDG_DATA_HOME reports
    # ".../share/../bin", the same directory as ".local/bin" (#1080).
    if [ "$_rk_parent_active" = "$_rk_expected_path" ]; then
        return 0
    fi
    if [ -n "$_rk_parent_active" ]; then
        _rk_active_dir=$(cd -P "$(dirname "$_rk_parent_active")" 2>/dev/null && pwd -P) || _rk_active_dir=
        _rk_expected_dir=$(cd -P "$(dirname "$_rk_expected_path")" 2>/dev/null && pwd -P) || _rk_expected_dir=
        if [ -n "$_rk_active_dir" ] && [ "$_rk_active_dir" = "$_rk_expected_dir" ] &&
            [ "$(basename "$_rk_parent_active")" = "$(basename "$_rk_expected_path")" ]; then
            return 0
        fi
    fi
    if [ -n "$_rk_parent_active" ]; then
        log_error "After this installer exits, PATH resolves $_rk_name to $_rk_parent_active."
        log_error "That file was preserved. Move it aside or put $(dirname "$_rk_expected_path") earlier in PATH, then retry."
        return 1
    else
        _rk_path_activation_required=1
        _rk_path_activation_dir=$(dirname "$_rk_expected_path")
        log_warning "Installed $_rk_name is not on the PATH inherited by this installer."
    fi
    return 0
}

setup_claude_mcp() {
    if ! command -v claude >/dev/null 2>&1; then
        log_info "Claude Code not detected — skipping MCP setup."
        return 0
    fi

    _rk_uv_bin=$(_rk_uv_resolve tool dir --bin 2>/dev/null || printf '%s/.local/bin' "$HOME")
    _rk_uv_channel="${_rk_uv_bin}/rookone-channel"

    if claude mcp add --scope user rookone -- rookone-channel >/dev/null 2>&1; then
        _rk_mcp_registered=1
        log_success "Registered the 'rookone' MCP server in Claude Code (user scope)."
        log_success "It resolves from any directory — launch with:"
        log_success "  claude --dangerously-load-development-channels=server:rookone"
    elif _rk_mcp_output=$(claude mcp get rookone 2>/dev/null); then
        _rk_mcp_command=$(printf '%s\n' "$_rk_mcp_output" | awk '
            /^[[:space:]]*Command:[[:space:]]*/ {
                sub(/^[[:space:]]*Command:[[:space:]]*/, "")
                print
                exit
            }
        ')
        _rk_mcp_path=$(printf '%s\n' "$_rk_mcp_output" | awk '
            /^[[:space:]]*Environment:[[:space:]]*$/ { environment = 1; next }
            environment && /^[[:space:]]*PATH=/ {
                sub(/^[[:space:]]*PATH=/, "")
                print
                exit
            }
        ')
        case "$_rk_mcp_command" in
            rookone-channel)
                if [ -z "$_rk_mcp_path" ]; then
                    _rk_mcp_registered=1
                    log_info "The existing 'rookone' MCP server is already registered with the verified generic channel command."
                else
                    log_warning "The existing 'rookone' MCP registration pins PATH=$_rk_mcp_path; left unchanged and unverified."
                    log_warning "To use this release, run:"
                    log_warning "  claude mcp remove rookone -s user"
                    log_warning "  claude mcp add --scope user rookone -- $_rk_uv_channel"
                fi
                ;;
            "$_rk_uv_channel")
                _rk_mcp_registered=1
                log_info "The existing 'rookone' MCP registration uses the verified uv-owned channel command."
                ;;
            *)
                log_warning "The existing 'rookone' MCP registration uses ${_rk_mcp_command:-an unknown command}; left unchanged and unverified."
                log_warning "To use this release, run:"
                log_warning "  claude mcp remove rookone -s user"
                log_warning "  claude mcp add --scope user rookone -- $_rk_uv_channel"
                ;;
        esac
    else
        log_warning "Could not register the rookone MCP server automatically. Run:"
        log_warning "  claude mcp add --scope user rookone -- rookone-channel"
    fi
}

# `codex mcp add` silently replaces an entry of the same name, so look first
# and never overwrite a registration the user made.
setup_codex_mcp() {
    if ! command -v codex >/dev/null 2>&1; then
        log_info "Codex not detected — skipping Codex MCP setup."
        return 0
    fi
    if codex mcp get rookone >/dev/null 2>&1; then
        log_info "Codex already has an MCP server named 'rookone'; left unchanged."
    elif codex mcp add rookone -- rookone mcp >/dev/null 2>&1; then
        log_success "Registered the 'rookone' MCP server in Codex (rookone mcp)."
    else
        log_warning "Could not register the rookone MCP server in Codex. Run:"
        log_warning "  codex mcp add rookone -- rookone mcp"
    fi
}

setup_claude_statusline() {
    if [ -n "${ROOKONE_NO_STATUSLINE:-}" ]; then
        log_info "ROOKONE_NO_STATUSLINE set — skipping status line setup."
        return 0
    fi

    if ! command -v claude >/dev/null 2>&1; then
        log_info "Claude Code not detected — skipping status line setup."
        return 0
    fi

    _rk_bin=$(command -v rookone 2>/dev/null || true)
    if [ -z "$_rk_bin" ]; then
        log_warning "rookone not on PATH — skipping status line setup."
        return 0
    fi

    if rookone claude install-statusline --binary "$_rk_bin" >/dev/null 2>&1; then
        log_success "Configured the Claude Code status line (RookOne agent state)."
        log_info "  Opt out next time with ROOKONE_NO_STATUSLINE=1."
        log_info "  Remove it with: rookone claude uninstall-statusline"
    else
        log_warning "Could not configure the Claude Code status line (skipped)."
        log_warning "  Set it up later with: rookone claude install-statusline"
    fi
}

print_next_steps() {
    printf '\n'
    log_success "rookone installed successfully!"
    printf '\n'
    if [ "$_rk_path_activation_required" = "1" ]; then
        _rk_quoted_path=$(printf '%s' "$_rk_path_activation_dir" | sed 's/[\\"]/\\&/g; s/[$`]/\\&/g')
        printf 'Current terminal: add the RookOne install directory to PATH before running it:\n'
        printf '  export PATH="%s:$PATH"\n' "$_rk_quoted_path"
        printf 'For future terminals, run `uv tool update-shell` after the current-shell command, then open a new terminal.\n'
        printf '\n'
    fi
    printf 'Getting started:\n'
    printf '  rookone --help          show all commands\n'
    printf '  rookone account add     sign in\n'
    printf '  rookone init            create your first agent\n'
    printf '  rookone whoami          confirm your identity\n'
    printf '  rookone inbox           check for new messages\n'
    printf '  rookone send            send a direct message\n'
    printf '\n'
    printf 'Claude Code (MCP):\n'
    if [ "$_rk_mcp_registered" = "1" ]; then
        printf '  The "rookone" MCP server is registered (user scope) — works from any\n'
        printf '  directory. Start a session with real-time messaging:\n'
        printf '    claude --dangerously-load-development-channels=server:rookone\n'
    elif command -v claude >/dev/null 2>&1; then
        printf '  Automatic MCP registration did not complete. Run:\n'
        printf '    claude mcp add --scope user rookone -- rookone-channel\n'
    else
        printf '  Claude Code was not detected. Install it, then re-run this installer to\n'
        printf '  install and register the optional channel integration.\n'
    fi
    printf '\n'
    printf 'Docs: https://docs.rookone.io\n'
    printf '\n'
    printf 'The tools are installed; no agent exists until you run: rookone init\n'
    printf 'Once you have one, run rookone agent backup: its root key is kept outside\n'
    printf 'the .rookone directory, so keeping that directory alone is not enough.\n'
    printf '\n'
}

main() {
    if parse_args "$@"; then
        :
    else
        _rk_parse_status=$?
        [ "$_rk_parse_status" -eq 2 ] && return 0
        return "$_rk_parse_status"
    fi
    _rk_validate_index_url "$ROOKONE_INDEX_URL" || return 1
    log_info "RookOne installer starting"
    assert_supported_platform
    ensure_uv
    load_pipx_state || return 1
    install_rookone || return 1
    verify_installation || return 1
    setup_claude_mcp || return 1
    setup_codex_mcp
    setup_claude_statusline
    print_next_steps
}

if [ -z "${ROOKONE_INSTALL_LIB:-}" ]; then
    main "$@"
fi
