#!/bin/bash
#
# IvyBloom CLI Installer
#
# Usage:
#   curl -fsSL https://ivybloom.com/install.sh | bash
#
# Options:
#   --method=<method>   Installation method: auto, binary, brew, npm, source
#   --version=<ver>     Specific version (default: latest)
#   --no-verify         Skip post-install verification
#   --skip-actions      Skip actions environment installation (installed by default)
#   --python-only       Install Python packages only
#   --binaries-only     Install binaries only
#
# This script automatically:
#   1. Detects your OS and architecture
#   2. Downloads pre-built binary (or uses package manager)
#   3. Installs to /usr/local/bin or ~/.local/bin
#

set -e

# Handle interrupts gracefully
cleanup_spinner() {
    if [[ -n "${SPINNER_PID:-}" ]]; then
        kill "$SPINNER_PID" 2>/dev/null || true
        wait "$SPINNER_PID" 2>/dev/null || true
        printf "\r\033[K"  # Clear spinner line
    fi
}
trap cleanup_spinner EXIT INT TERM

# ============================================================================
# Configuration
# ============================================================================

REPO="ivybiosciences/ivybloom-cli"
BINARY_NAME="ivybloom"
CONFIG_URL="https://www.ivybiosciences.com/api/cli/install-config"
DEFAULT_CDN_URL="https://releases.ivybiosciences.com/releases"
VERSION="${VERSION:-latest}"
METHOD="${METHOD:-auto}"
VERIFY="${VERIFY:-true}"

# PATH setup state (populated by setup_path)
INSTALL_DIR=""
PATH_ALREADY_SET="true"
DETECTED_SHELL=""
SHELL_PROFILE=""
SHELL_EXPORT=""

# ============================================================================
# Colors and Output
# ============================================================================

# IvyBloom Brand Colors (hex for gum, ANSI fallback)
BRAND_GREEN="#b7c6b3"
BRAND_DARK="#232220"
BRAND_ERROR="#d17070"
BRAND_WARNING="#ffeaa7"
BRAND_INFO="#7fa3c9"
BRAND_PENDING="#c9a87f"

# Detect terminal capabilities
if [[ -t 1 ]] && [[ "${TERM:-}" != "dumb" ]]; then
    BOLD='\033[1m'
    DIM='\033[2m'
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    BLUE='\033[0;34m'
    CYAN='\033[0;36m'
    NC='\033[0m'
    HAS_COLOR="true"
else
    BOLD='' DIM='' RED='' GREEN='' YELLOW='' BLUE='' CYAN='' NC=''
    HAS_COLOR="false"
fi

# Detect gum for enhanced animations
HAS_GUM="false"
if command -v gum &>/dev/null; then
    HAS_GUM="true"
fi

# ============================================================================
# Animated Banner and Output Functions
# ============================================================================

print_banner() {
    echo ""
    
    if [[ "$HAS_GUM" == "true" ]]; then
        # Use gum for a beautiful styled banner
        local banner_text="  ___              ____  _                _
 |_ _|_   ___   _ | __ )(_) ___  ___  ___(_) ___ _ __   ___ ___  ___
  | |\\ \\ / / | | ||  _ \\| |/ _ \\/ __|/ __| |/ _ \\ '_ \\ / __/ _ \\/ __|
  | | \\ V /| |_| || |_) | | (_) \\__ \\ (__| |  __/ | | | (_|  __/\\__ \\
 |___| \\_/  \\__, ||____/|_|\\___/|___/\\___|_|\\___|_| |_|\\___\\___||___/
            |___/                                         .bloom*"
        
        gum style \
            --foreground "$BRAND_GREEN" \
            --bold \
            "$banner_text"
        
        gum style \
            --foreground "$BRAND_DARK" \
            --faint \
            --margin "0 0 1 2" \
            "Computational Biology & Drug Discovery CLI"
    else
        # Fallback to basic ANSI
        echo -e "${GREEN}${BOLD}"
        cat << 'EOF'
  ___              ____  _                _
 |_ _|_   ___   _ | __ )(_) ___  ___  ___(_) ___ _ __   ___ ___  ___
  | |\ \ / / | | ||  _ \| |/ _ \/ __|/ __| |/ _ \ '_ \ / __/ _ \/ __|
  | | \ V /| |_| || |_) | | (_) \__ \ (__| |  __/ | | | (_|  __/\__ \
 |___| \_/  \__, ||____/|_|\___/|___/\___|_|\___|_| |_|\___\___||___/
            |___/
                                                          .bloom*
EOF
        echo -e "${NC}"
        echo -e "${DIM}Computational Biology & Drug Discovery CLI${NC}"
    fi
    echo ""
}

# Log functions with optional gum styling
# Note: gum style applies to entire text, so we format inline with printf
log_step() {
    if [[ "$HAS_GUM" == "true" ]]; then
        printf "%s %s\n" "$(gum style --foreground "$BRAND_INFO" ">")" "$1"
    else
        echo -e "${CYAN}>${NC} $1"
    fi
}

log_success() {
    if [[ "$HAS_GUM" == "true" ]]; then
        printf "%s %s\n" "$(gum style --foreground "$BRAND_GREEN" --bold "✓")" "$1"
    else
        echo -e "${GREEN}✓${NC} $1"
    fi
}

log_error() {
    if [[ "$HAS_GUM" == "true" ]]; then
        printf "%s %s\n" "$(gum style --foreground "$BRAND_ERROR" --bold "✗")" "$1" >&2
    else
        echo -e "${RED}✗${NC} $1" >&2
    fi
}

log_warning() {
    if [[ "$HAS_GUM" == "true" ]]; then
        printf "%s %s\n" "$(gum style --foreground "$BRAND_WARNING" "!")" "$1"
    else
        echo -e "${YELLOW}!${NC} $1"
    fi
}

die() { log_error "$1"; exit 1; }

# ============================================================================
# Spinner Functions (gum spin wrapper)
# ============================================================================

# Run a command with an animated spinner
# Usage: spin_exec "Downloading..." command arg1 arg2
spin_exec() {
    local title="$1"
    shift
    
    if [[ "$HAS_GUM" == "true" ]]; then
        # Use gum spin with IvyBloom-themed spinner
        gum spin \
            --spinner "dot" \
            --spinner.foreground "$BRAND_GREEN" \
            --title "$title" \
            --title.foreground "$BRAND_INFO" \
            -- "$@"
        return $?
    else
        # Fallback: show message then run command
        echo -ne "${CYAN}⠋${NC} $title\r"
        "$@"
        local result=$?
        echo -ne "\033[2K\r"  # Clear line
        return $result
    fi
}

# Run a command with spinner, showing output on error
# Usage: spin_exec_show_error "Building..." command arg1 arg2
spin_exec_show_error() {
    local title="$1"
    shift
    
    if [[ "$HAS_GUM" == "true" ]]; then
        gum spin \
            --spinner "dot" \
            --spinner.foreground "$BRAND_GREEN" \
            --title "$title" \
            --title.foreground "$BRAND_INFO" \
            --show-error \
            -- "$@"
        return $?
    else
        echo -ne "${CYAN}⠋${NC} $title\r"
        local output
        output=$("$@" 2>&1)
        local result=$?
        echo -ne "\033[2K\r"
        if [[ $result -ne 0 ]]; then
            echo "$output" >&2
        fi
        return $result
    fi
}

# Simple spinner animation (non-blocking, for manual control)
# Usage: start_spinner "Loading..." ; ... ; stop_spinner
SPINNER_PID=""

start_spinner() {
    local message="$1"
    
    if [[ "$HAS_GUM" == "true" ]]; then
        # Gum spin requires a command, so we use a different approach for manual spinners
        (
            local chars=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
            local i=0
            while true; do
                printf "\r\033[K%s %s" "$(gum style --foreground "$BRAND_GREEN" "${chars[$i]}")" "$message"
                i=$(( (i+1) % 10 ))
                sleep 0.1
            done
        ) &
        SPINNER_PID=$!
    else
        (
            local chars=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
            local i=0
            while true; do
                printf "\r${CYAN}%s${NC} %s" "${chars[$i]}" "$message"
                i=$(( (i+1) % 10 ))
                sleep 0.1
            done
        ) &
        SPINNER_PID=$!
    fi
    disown
}

stop_spinner() {
    if [[ -n "$SPINNER_PID" ]]; then
        kill "$SPINNER_PID" 2>/dev/null || true
        wait "$SPINNER_PID" 2>/dev/null || true
        SPINNER_PID=""
        printf "\r\033[K"  # Clear the spinner line
    fi
}

# ============================================================================
# Parse Arguments
# ============================================================================

VERIFY_ONLY="false"
SKIP_ACTIONS_ENV="false"
PYTHON_ONLY="false"
BINARIES_ONLY="false"

for arg in "$@"; do
    case "$arg" in
        --method=*)       METHOD="${arg#*=}" ;;
        --version=*)      VERSION="${arg#*=}" ;;
        --no-verify)      VERIFY="false" ;;
        --verify)         VERIFY_ONLY="true" ;;
        --skip-actions)   SKIP_ACTIONS_ENV="true" ;;
        --python-only)    PYTHON_ONLY="true" ;;
        --binaries-only)  BINARIES_ONLY="true" ;;
        --help|-h)
            echo "Usage: install.sh [OPTIONS]"
            echo ""
            echo "Options:"
            echo "  --method=METHOD   auto, binary, brew, npm, choco, source"
            echo "  --version=VER     Specific version (default: latest)"
            echo "  --no-verify       Skip verification after install"
            echo "  --verify          Verify existing installation (no install)"
            echo ""
            echo "Actions Environment (installed by default):"
            echo "  --skip-actions    Skip actions environment installation"
            echo "  --python-only     Install Python packages only"
            echo "  --binaries-only   Install binaries only"
            exit 0
            ;;
    esac
done

# ============================================================================
# Utility Functions
# ============================================================================

command_exists() { command -v "$1" &>/dev/null; }

# Get CDN URL: env var > config endpoint > default
get_cdn_url() {
    # 1. Check environment variable override
    if [[ -n "${IVYBLOOM_CDN_URL:-}" ]]; then
        echo "$IVYBLOOM_CDN_URL"
        return 0
    fi

    # 2. Try fetching from config endpoint
    local config_cdn=""
    if command_exists curl; then
        config_cdn=$(curl -fsSL --connect-timeout 5 "$CONFIG_URL" 2>/dev/null | grep -o '"cdn_url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"cdn_url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' 2>/dev/null || true)
    elif command_exists wget; then
        config_cdn=$(wget -qO- --timeout=5 "$CONFIG_URL" 2>/dev/null | grep -o '"cdn_url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"cdn_url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' 2>/dev/null || true)
    fi

    if [[ -n "$config_cdn" ]]; then
        echo "$config_cdn"
        return 0
    fi

    # 3. Fallback to default (with warning)
    log_warning "Could not fetch CDN config, using default"
    echo "$DEFAULT_CDN_URL"
    return 0
}

# Initialize CDN URL (called once at start)
RELEASES_BASE_URL=""
init_cdn_url() {
    if [[ -z "$RELEASES_BASE_URL" ]]; then
        RELEASES_BASE_URL=$(get_cdn_url)
    fi
}

get_os() {
    case "$(uname -s | tr '[:upper:]' '[:lower:]')" in
        darwin)  echo "darwin" ;;
        linux)   echo "linux" ;;
        mingw*|msys*|cygwin*) echo "windows" ;;
        *)       echo "unknown" ;;
    esac
}

get_arch() {
    case "$(uname -m)" in
        x86_64|amd64)  echo "amd64" ;;
        aarch64|arm64) echo "arm64" ;;
        *)             echo "unknown" ;;
    esac
}

get_latest_version() {
    local url="${RELEASES_BASE_URL}/latest.txt"
    local version
    if command_exists curl; then
        version=$(curl -fsSL "$url" 2>/dev/null | tr -d '[:space:]')
    elif command_exists wget; then
        version=$(wget -qO- "$url" 2>/dev/null | tr -d '[:space:]')
    fi
    # Remove 'v' prefix if present
    echo "${version#v}"
}

download() {
    local url="$1" output="$2"
    if command_exists curl; then
        curl -fsSL "$url" -o "$output"
    elif command_exists wget; then
        wget -q "$url" -O "$output"
    else
        die "Neither curl nor wget found"
    fi
}

# Animated download with spinner
download_with_spinner() {
    local url="$1" output="$2" message="${3:-Downloading...}"
    
    if [[ "$HAS_GUM" == "true" ]]; then
        if command_exists curl; then
            gum spin \
                --spinner "dot" \
                --spinner.foreground "$BRAND_GREEN" \
                --title "$message" \
                --title.foreground "$BRAND_INFO" \
                -- curl -fsSL "$url" -o "$output"
        elif command_exists wget; then
            gum spin \
                --spinner "dot" \
                --spinner.foreground "$BRAND_GREEN" \
                --title "$message" \
                --title.foreground "$BRAND_INFO" \
                -- wget -q "$url" -O "$output"
        else
            die "Neither curl nor wget found"
        fi
    else
        download "$url" "$output"
    fi
}

get_install_dir() {
    if [[ -w "/usr/local/bin" ]]; then
        echo "/usr/local/bin"
    else
        mkdir -p "$HOME/.local/bin"
        echo "$HOME/.local/bin"
    fi
}

# ============================================================================
# Package Manager Installation (for actions environment)
# ============================================================================

install_homebrew_if_needed() {
    if command_exists brew; then
        export HOMEBREW_NO_AUTO_UPDATE=1
        return 0
    fi

    local os=$(get_os)
    if [[ "$os" != "darwin" ]]; then
        return 1
    fi

    log_step "Installing Homebrew..."
    if ! command_exists curl; then
        log_error "curl not found. Please install curl first."
        return 1
    fi

    if /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 2>/dev/null; then
        log_success "Homebrew installed"
        export HOMEBREW_NO_AUTO_UPDATE=1
        return 0
    else
        log_error "Failed to install Homebrew"
        return 1
    fi
}

install_python_if_needed() {
    if command_exists python3; then
        return 0
    fi

    log_step "Installing Python 3..."

    local os=$(get_os)
    case "$os" in
        darwin)
            if command_exists brew; then
                brew install python3 2>/dev/null && return 0
            fi
            log_step "Downloading Python from python.org..."
            ;;
        linux)
            if command_exists apt-get; then
                sudo apt-get update -qq && sudo apt-get install -y python3 python3-pip 2>/dev/null && return 0
            elif command_exists dnf; then
                sudo dnf install -y python3 python3-pip 2>/dev/null && return 0
            elif command_exists yum; then
                sudo yum install -y python3 python3-pip 2>/dev/null && return 0
            fi
            ;;
        windows)
            log_step "Downloading Python from python.org..."
            ;;
    esac

    local arch=$(get_arch)
    local python_pkg="python-3.11.8-macos11.pkg"
    [[ "$arch" == "arm64" ]] && python_pkg="python-3.11.8-macos11-arm64.pkg"

    log_error "Could not install Python automatically. Please install from https://python.org"
    return 1
}

install_npm_if_needed() {
    if command_exists npm; then
        return 0
    fi

    log_step "Installing Node.js and npm..."

    local os=$(get_os)
    case "$os" in
        darwin)
            if command_exists brew; then
                brew install node 2>/dev/null && return 0
            fi
            ;;
        linux)
            if command_exists apt-get; then
                sudo apt-get install -y nodejs npm 2>/dev/null && return 0
            fi
            ;;
    esac

    log_step "Downloading Node.js from nodejs.org..."
    local arch=$(get_arch)
    local node_version="v20.11.0"
    local pkg="node-${node_version}-linux-${arch}.tar.xz"

    return 1
}

install_chocolatey_if_needed() {
    if command_exists choco; then
        return 0
    fi

    log_step "Installing Chocolatey..."

    if ! command_exists powershell; then
        log_error "PowerShell not found. Cannot install Chocolatey."
        return 1
    fi

    if powershell -Command "Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" 2>/dev/null; then
        log_success "Chocolatey installed"
        return 0
    else
        log_error "Failed to install Chocolatey"
        return 1
    fi
}

install_conda_if_needed() {
    if command_exists conda; then
        return 0
    fi

    log_step "Installing Miniconda..."

    local os=$(get_os)
    local arch=$(get_arch)
    local tmp_dir=$(mktemp -d)
    local installer="Miniconda3-latest-${os}-${arch}.sh"

    # Use correct URL pattern for Miniconda (repo.continuum.io redirects to repo.anaconda.com)
    local url
    case "$os" in
        darwin)
            if [[ "$arch" == "arm64" ]]; then
                url="https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-arm64.sh"
            else
                url="https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh"
            fi
            ;;
        linux)
            url="https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-${arch}.sh"
            ;;
        *)
            rm -rf $tmp_dir
            log_warning "Unsupported OS for Miniconda"
            return 1
            ;;
    esac

    trap "rm -rf $tmp_dir" EXIT

    if download_with_spinner "$url" "$tmp_dir/$installer" "Downloading Miniconda..."; then
        # Check if directory already exists
        if [[ -d "$HOME/miniconda3" ]]; then
            log_step "Conda directory exists, updating..."
            if spin_exec "Updating Miniconda..." bash "$tmp_dir/$installer" -u -b -p "$HOME/miniconda3" 2>&1; then
                export PATH="$HOME/miniconda3/bin:$PATH"
            else
                log_warning "Conda update failed, using existing installation"
            fi
        else
            if spin_exec "Installing Miniconda..." bash "$tmp_dir/$installer" -b -p "$HOME/miniconda3" 2>&1; then
                export PATH="$HOME/miniconda3/bin:$PATH"
            else
                log_error "Miniconda installer failed"
            fi
        fi

        # Initialize conda if the binary exists
        if [[ -x "$HOME/miniconda3/bin/conda" ]]; then
            local shell_name=$(basename "${SHELL:-/bin/bash}")
            log_step "Initializing conda for $shell_name..."
            if [[ "$shell_name" == "zsh" ]]; then
                "$HOME/miniconda3/bin/conda" init zsh 2>&1 || true
            else
                "$HOME/miniconda3/bin/conda" init bash 2>&1 || true
            fi
            log_success "Miniconda ready at $HOME/miniconda3"
            rm -rf $tmp_dir
            return 0
        else
            log_warning "Conda binary not found at expected location"
        fi
    else
        log_error "Failed to download Miniconda"
    fi

    rm -rf $tmp_dir
    log_warning "Could not install Miniconda automatically"
    echo -e "  ${DIM}Install manually: https://docs.conda.io/en/latest/miniconda.html${NC}"
    return 1
}

install_pip_if_needed() {
    if command_exists pip3 || command_exists pip; then
        return 0
    fi

    log_step "Installing pip..."

    if command_exists python3; then
        python3 -m ensurepip --default-pip 2>/dev/null && return 0
    fi

    if command_exists curl; then
        curl -fsSL https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py 2>/dev/null
        if [[ -f /tmp/get-pip.py ]]; then
            python3 /tmp/get-pip.py 2>/dev/null && return 0
            rm /tmp/get-pip.py
        fi
    fi

    log_error "Failed to install pip"
    return 1
}

# ============================================================================
# Installation Methods
# ============================================================================

install_with_brew() {
    log_step "Installing with Homebrew..."

    if ! command_exists brew; then
        die "Homebrew not installed. Run: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
    fi

    export HOMEBREW_NO_AUTO_UPDATE=1

    local formula_url="${RELEASES_BASE_URL}/homebrew/ivybloom.rb"
    log_step "Formula: $formula_url"

    if HOMEBREW_NO_AUTO_UPDATE=1 brew install --formula "$formula_url" 2>/dev/null; then
        log_success "Installed via Homebrew"
    else
        log_warning "Homebrew formula not available, falling back to binary install"
        install_binary
    fi
}

install_with_npm() {
    log_step "Installing with npm..."

    if ! command_exists npm; then
        die "npm not installed. Install Node.js first: https://nodejs.org"
    fi

    if npm install -g ivybloom 2>/dev/null; then
        log_success "Installed via npm"
    else
        log_warning "npm package not available, falling back to binary install"
        install_binary
    fi
}

install_with_choco() {
    log_step "Installing with Chocolatey..."

    if ! command_exists choco; then
        die "Chocolatey not installed. See: https://chocolatey.org/install"
    fi

    if choco install ivybloom -y --no-progress 2>/dev/null; then
        log_success "Installed via Chocolatey"
    else
        log_warning "Chocolatey package not available, falling back to binary install"
        install_binary
    fi
}

install_binary() {
    log_step "Installing pre-built binary..."

    local os=$(get_os)
    local arch=$(get_arch)

    if [[ "$os" == "unknown" ]]; then
        log_warning "Unsupported OS: $(uname -s), falling back to source build"
        install_from_source
        return $?
    fi

    if [[ "$arch" == "unknown" ]]; then
        log_warning "Unsupported architecture: $(uname -m), falling back to source build"
        install_from_source
        return $?
    fi

    # Get version
    local version="$VERSION"
    if [[ "$version" == "latest" ]]; then
        log_step "Fetching latest version..."
        version=$(get_latest_version 2>/dev/null || echo "")
        if [[ -z "$version" ]]; then
            log_warning "No releases found, falling back to source build"
            install_from_source
            return $?
        fi
    fi
    version="${version#v}"  # Remove 'v' prefix if present

    log_step "Version: $version"

    # Determine asset name
    local ext="tar.gz"
    [[ "$os" == "windows" ]] && ext="zip"
    local asset="ivybloom_${version}_${os}_${arch}.${ext}"
    local url="${RELEASES_BASE_URL}/${version}/${asset}"

    # Download
    local tmp_dir=$(mktemp -d)
    trap "rm -rf $tmp_dir" EXIT

    if ! download_with_spinner "$url" "$tmp_dir/$asset" "Downloading $asset..."; then
        # Fallback to plain binary name (old release format)
        local fallback_name="${BINARY_NAME}-${os}-${arch}"
        [[ "$os" == "windows" ]] && fallback_name="${fallback_name}.exe"
        url="${RELEASES_BASE_URL}/${version}/${fallback_name}"

        if ! download_with_spinner "$url" "$tmp_dir/$BINARY_NAME" "Downloading binary..."; then
            log_warning "Binary download failed, falling back to source build"
            install_from_source
            return $?
        fi

        local install_dir=$(get_install_dir)
        if [[ -w "$install_dir" ]]; then
            spin_exec "Installing binary..." mv "$tmp_dir/$BINARY_NAME" "$install_dir/"
            chmod +x "$install_dir/$BINARY_NAME"
        else
            spin_exec "Installing binary (sudo)..." sudo mv "$tmp_dir/$BINARY_NAME" "$install_dir/"
            sudo chmod +x "$install_dir/$BINARY_NAME"
        fi
        log_success "Binary installed to $install_dir"
        return 0
    fi

    # Extract with spinner
    if [[ "$ext" == "zip" ]]; then
        spin_exec "Extracting archive..." unzip -q "$tmp_dir/$asset" -d "$tmp_dir"
    else
        spin_exec "Extracting archive..." tar -xzf "$tmp_dir/$asset" -C "$tmp_dir"
    fi

    # Install
    local install_dir=$(get_install_dir)
    local binary="$BINARY_NAME"
    [[ "$os" == "windows" ]] && binary="${BINARY_NAME}.exe"

    if [[ -w "$install_dir" ]]; then
        mv "$tmp_dir/$binary" "$install_dir/"
        chmod +x "$install_dir/$binary"
    else
        sudo mv "$tmp_dir/$binary" "$install_dir/"
        sudo chmod +x "$install_dir/$binary"
    fi

    log_success "Binary installed to $install_dir/$binary"
}

install_go_if_needed() {
    if command_exists go; then
        return 0
    fi

    log_step "Go not found, attempting to install..."

    local os=$(get_os)
    local arch=$(get_arch)

    # Try package managers first
    case "$os" in
        darwin)
            if command_exists brew; then
                log_step "Installing Go via Homebrew..."
                HOMEBREW_NO_AUTO_UPDATE=1 brew install go 2>/dev/null && return 0
            fi
            ;;
        linux)
            if command_exists apt-get; then
                log_step "Installing Go via apt..."
                sudo apt-get install -y golang 2>/dev/null && return 0
            elif command_exists dnf; then
                log_step "Installing Go via dnf..."
                sudo dnf install -y golang 2>/dev/null && return 0
            elif command_exists yum; then
                log_step "Installing Go via yum..."
                sudo yum install -y golang 2>/dev/null && return 0
            fi
            ;;
    esac

    # Fallback: download Go directly
    local go_version="1.24.0"
    local go_archive="go${go_version}.${os}-${arch}.tar.gz"
    local go_url="https://go.dev/dl/${go_archive}"
    local tmp_go_dir=$(mktemp -d)

    if download_with_spinner "$go_url" "$tmp_go_dir/$go_archive" "Downloading Go ${go_version}..."; then
        spin_exec "Installing Go..." sudo rm -rf /usr/local/go
        spin_exec "Extracting Go..." sudo tar -C /usr/local -xzf "$tmp_go_dir/$go_archive"
        export PATH="/usr/local/go/bin:$PATH"
        rm -rf "$tmp_go_dir"

        if command_exists go; then
            log_success "Go installed successfully"
            return 0
        fi
    fi

    rm -rf "$tmp_go_dir"
    return 1
}

install_from_source() {
    log_step "Building from source..."

    if ! command_exists go; then
        if ! install_go_if_needed; then
            log_error "Go is required but could not be installed automatically"
            echo ""
            echo "  Install Go manually from: https://go.dev/dl/"
            echo ""
            echo "  macOS:   brew install go"
            echo "  Linux:   sudo apt install golang  (or dnf/yum)"
            echo "  Windows: choco install golang"
            echo ""
            return 1
        fi
    fi

    local source_dir=""
    local need_cleanup="false"

    # Check if we're already in a local source directory
    local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    if [[ -f "$script_dir/main.go" ]] && [[ -f "$script_dir/go.mod" ]]; then
        # Running from within the go/ directory
        log_step "Using local source directory..."
        source_dir="$script_dir"
    elif [[ -f "$script_dir/go/main.go" ]] && [[ -f "$script_dir/go/go.mod" ]]; then
        # Running from repo root
        log_step "Using local source directory..."
        source_dir="$script_dir/go"
    else
        # Need to download source
        local tmp_dir=$(mktemp -d)
        need_cleanup="true"
        trap "rm -rf $tmp_dir" EXIT

        if command_exists git; then
            if ! spin_exec "Cloning repository..." git clone --depth 1 "https://github.com/${REPO}.git" "$tmp_dir/ivybloom-cli" 2>/dev/null; then
                log_warning "Git clone failed, trying archive download..."
                if ! download_with_spinner "https://github.com/${REPO}/archive/refs/heads/main.tar.gz" "$tmp_dir/repo.tar.gz" "Downloading source archive..."; then
                    log_error "Failed to download source code"
                    return 1
                fi
                spin_exec "Extracting source..." tar -xzf "$tmp_dir/repo.tar.gz" -C "$tmp_dir"
                mv "$tmp_dir"/ivybloom-cli-* "$tmp_dir/ivybloom-cli"
            fi
        else
            if ! download_with_spinner "https://github.com/${REPO}/archive/refs/heads/main.tar.gz" "$tmp_dir/repo.tar.gz" "Downloading source archive..."; then
                log_error "Failed to download source code"
                return 1
            fi
            spin_exec "Extracting source..." tar -xzf "$tmp_dir/repo.tar.gz" -C "$tmp_dir"
            mv "$tmp_dir"/ivybloom-cli-* "$tmp_dir/ivybloom-cli"
        fi
        source_dir="$tmp_dir/ivybloom-cli/go"
    fi

    # Build from source directory
    local build_dir="$source_dir"
    cd "$build_dir"
    
    if ! spin_exec_show_error "Building from source..." env CGO_ENABLED=0 go build -ldflags="-s -w" -o "$BINARY_NAME" .; then
        log_error "Build failed"
        return 1
    fi

    local install_dir=$(get_install_dir)
    local dest="$install_dir/$BINARY_NAME"
    local src_path=$(realpath "$BINARY_NAME" 2>/dev/null || echo "$PWD/$BINARY_NAME")
    local dest_path=$(realpath "$dest" 2>/dev/null || echo "")

    # Only move if source and destination are different
    if [[ "$src_path" != "$dest_path" ]]; then
        if [[ -w "$install_dir" ]]; then
            mv "$BINARY_NAME" "$install_dir/"
        else
            sudo mv "$BINARY_NAME" "$install_dir/"
        fi
    else
        log_info "Binary already in install directory"
    fi

    # Cleanup temp dir if we created one
    if [[ "$need_cleanup" == "true" ]] && [[ -n "${tmp_dir:-}" ]]; then
        rm -rf "$tmp_dir"
    fi

    log_success "Built and installed to $install_dir"
}

# ============================================================================
# Auto-detect Best Method
# ============================================================================

detect_best_method() {
    local os=$(get_os)

    # Prefer source build when Go is available (most reliable)
    if command_exists go; then
        echo "source"
        return
    fi

    # Try package managers (they fallback to binary -> source anyway)
    case "$os" in
        darwin)
            command_exists brew && echo "brew" && return
            ;;
        windows)
            command_exists choco && echo "choco" && return
            ;;
    esac

    command_exists npm && echo "npm" && return

    # Default to source (will attempt to install Go if needed)
    echo "source"
}

# ============================================================================
# Post-Install
# ============================================================================

setup_path() {
    local install_dir=$(get_install_dir)
    INSTALL_DIR="$install_dir"  # Export for print_next_steps

    if [[ ":$PATH:" == *":$install_dir:"* ]]; then
        PATH_ALREADY_SET="true"
        return 0
    fi

    PATH_ALREADY_SET="false"
    log_warning "$install_dir is not in PATH"

    local shell_name=$(basename "${SHELL:-/bin/bash}")
    DETECTED_SHELL="$shell_name"

    case "$shell_name" in
        zsh)
            SHELL_PROFILE="$HOME/.zshrc"
            SHELL_EXPORT="export PATH=\"$install_dir:\$PATH\""
            ;;
        bash)
            if [[ -f "$HOME/.bashrc" ]]; then
                SHELL_PROFILE="$HOME/.bashrc"
            elif [[ -f "$HOME/.bash_profile" ]]; then
                SHELL_PROFILE="$HOME/.bash_profile"
            else
                SHELL_PROFILE="$HOME/.profile"
            fi
            SHELL_EXPORT="export PATH=\"$install_dir:\$PATH\""
            ;;
        fish)
            SHELL_PROFILE="$HOME/.config/fish/config.fish"
            SHELL_EXPORT="fish_add_path $install_dir"
            mkdir -p "$HOME/.config/fish"
            ;;
        *)
            SHELL_PROFILE="$HOME/.profile"
            SHELL_EXPORT="export PATH=\"$install_dir:\$PATH\""
            ;;
    esac

    # Add to shell profile
    echo "$SHELL_EXPORT" >> "$SHELL_PROFILE"
    log_success "Added PATH to $SHELL_PROFILE"
}

verify_install() {
    log_step "Verifying installation..."

    # Refresh PATH
    export PATH="$(get_install_dir):$PATH"

    local errors=0

    # Check binary
    if command_exists ivybloom; then
        local ver=$(ivybloom --version 2>/dev/null | head -1 || echo "installed")
        log_success "IvyBloom CLI: $ver"
        local path=$(command -v ivybloom)
        echo -e "  ${DIM}Location: $path${NC}"
    else
        log_error "IvyBloom CLI not found in PATH"
        errors=$((errors + 1))
    fi

    # Check directories
    local config_dir="$HOME/.config/ivybloom"
    local data_dir="$HOME/.local/share/ivybloom"

    if [[ -d "$config_dir" ]]; then
        log_success "Config directory exists"
    else
        log_warning "Creating config directory"
        mkdir -p "$config_dir"
    fi

    if [[ -d "$data_dir" ]]; then
        log_success "Data directory exists"
    else
        log_warning "Creating data directory"
        mkdir -p "$data_dir"
    fi

    # Check authentication
    if [[ -f "$config_dir/auth_token" ]] || [[ -f "$config_dir/api_key" ]]; then
        log_success "Authentication configured"
    else
        log_warning "Not authenticated - run: ivybloom auth login"
    fi

    return $errors
}

# Verify-only mode (replaces setup.sh)
run_verify_only() {
    echo ""
    
    if [[ "$HAS_GUM" == "true" ]]; then
        gum style \
            --foreground "$BRAND_INFO" \
            --bold \
            "IvyBloom CLI - Verification"
        gum style \
            --foreground "$BRAND_DARK" \
            --faint \
            "════════════════════════════════════════"
    else
        echo -e "${CYAN}IvyBloom CLI - Verification${NC}"
        echo -e "${DIM}════════════════════════════════════════${NC}"
    fi
    echo ""

    verify_install
    local result=$?

    echo ""
    if [[ "$HAS_GUM" == "true" ]]; then
        gum style --foreground "$BRAND_DARK" --faint "────────────────────────────────────────"
    else
        echo -e "${DIM}────────────────────────────────────────${NC}"
    fi
    echo ""

    if [[ $result -eq 0 ]] && command_exists ivybloom; then
        if [[ "$HAS_GUM" == "true" ]]; then
            gum style --foreground "$BRAND_GREEN" --bold "All systems operational!"
        else
            echo -e "${GREEN}All systems operational!${NC}"
        fi
        echo ""
        echo "Commands:"
        echo "  ivybloom              Launch interactive TUI"
        echo "  ivybloom auth login   Authenticate"
        echo "  ivybloom --help       View all commands"
    else
        echo -e "${YELLOW}Installation incomplete${NC}"
        echo ""
        echo "Install with:"
        echo "  brew install ivybiosciences/ivybloom-cli/ivybloom"
        echo "  npm install -g ivybloom"
        echo "  $0  # Run this script without --verify"
    fi
    echo ""

    exit $result
}

print_next_steps() {
    echo ""
    
    if [[ "$HAS_GUM" == "true" ]]; then
        # Styled completion message
        gum style \
            --foreground "$BRAND_GREEN" \
            --bold \
            --border "rounded" \
            --border-foreground "$BRAND_GREEN" \
            --padding "0 2" \
            --margin "0 0 1 0" \
            "✓ Installation complete!"
    else
        echo -e "${GREEN}${BOLD}Installation complete!${NC}"
    fi
    echo ""

    # Show PATH setup instructions if needed
    if [[ "$PATH_ALREADY_SET" == "false" ]]; then
        echo -e "${CYAN}Post-installation setup${NC}"
        echo -e "${DIM}────────────────────────────────────────${NC}"
        echo ""
        echo "Add ${INSTALL_DIR:-~/.local/bin} to your PATH:"
        echo ""

        case "${DETECTED_SHELL:-bash}" in
            zsh)
                echo "For zsh:"
                echo -e "  ${DIM}echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.zshrc${NC}"
                echo -e "  ${DIM}source ~/.zshrc${NC}"
                ;;
            bash)
                echo "For bash:"
                echo -e "  ${DIM}echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.bashrc${NC}"
                echo -e "  ${DIM}source ~/.bashrc${NC}"
                ;;
            fish)
                echo "For fish:"
                echo -e "  ${DIM}fish_add_path ~/.local/bin${NC}"
                echo ""
                echo "Or add permanently:"
                echo -e "  ${DIM}echo 'fish_add_path ~/.local/bin' >> ~/.config/fish/config.fish${NC}"
                ;;
            *)
                echo "Add to your shell profile:"
                echo -e "  ${DIM}echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.profile${NC}"
                echo -e "  ${DIM}source ~/.profile${NC}"
                ;;
        esac

        echo ""
        echo -e "${YELLOW}!${NC} We've already added this to ${SHELL_PROFILE:-your profile}."
        echo "  Restart your terminal or run the source command above."
        echo ""
        echo -e "${DIM}────────────────────────────────────────${NC}"
    fi

    echo ""
    echo -e "${CYAN}Verify installation${NC}"
    echo ""
    echo -e "  ${DIM}ivybloom version${NC}"
    echo ""
    echo -e "${CYAN}Get started${NC}"
    echo ""
    echo "  ivybloom              Launch interactive TUI"
    echo "  ivybloom auth login   Authenticate with IvyBloom"
    echo "  ivybloom --help       View all commands"
    echo ""

    # Developer note
    if [[ -d ".git" ]] || [[ -f "go.mod" ]]; then
        echo -e "${DIM}────────────────────────────────────────${NC}"
        echo ""
        echo -e "${CYAN}For developers${NC}"
        echo ""
        echo "Use the Makefile for development builds:"
        echo -e "  ${DIM}cd go && make dev-link${NC}    # Symlink to /usr/local/bin (no PATH changes needed)"
        echo -e "  ${DIM}cd go && make dev-unlink${NC}  # Remove development symlink"
        echo ""
    fi
}

# ============================================================================
# Actions Environment Installation
# ============================================================================

# Curated Python packages for actions
# Matches docs/actions-modal-architecture.md
PYTHON_PACKAGES=(
    # Core Scientific
    "numpy>=1.24"
    "pandas>=2.0"
    "scipy>=1.11"
    "matplotlib>=3.7"
    # Bioinformatics
    "biopython>=1.81"
    "biotite>=0.37"
    "pyhmmer>=0.10"
    "pyfaidx>=0.7"
    # Chemistry
    "rdkit>=2023.09"
    # openbabel Python bindings require system library - use brew/conda binary instead
    "py3Dmol>=2.0"
    # ML
    "scikit-learn>=1.3"
    "torch>=2.0"
    "transformers>=4.30"
    # Structure
    "prody>=2.4"
    "mdtraj>=1.9"
    # Data
    "polars>=0.19"
    "pyarrow>=14.0"
    "h5py>=3.9"
    # Utilities
    "requests>=2.31"
    "httpx>=0.25"
    "pydantic>=2.0"
    "rich>=13.0"
    "csvkit>=1.1"
)

# Map pip package name to import name for checking
PYTHON_IMPORT_MAP=(
    "numpy:numpy"
    "pandas:pandas"
    "scipy:scipy"
    "matplotlib:matplotlib"
    "biopython:Bio"
    "biotite:biotite"
    "pyhmmer:pyhmmer"
    "pyfaidx:pyfaidx"
    "rdkit:rdkit"
    "py3Dmol:py3Dmol"
    "scikit-learn:sklearn"
    "torch:torch"
    "transformers:transformers"
    "prody:prody"
    "mdtraj:mdtraj"
    "polars:polars"
    "pyarrow:pyarrow"
    "h5py:h5py"
    "requests:requests"
    "httpx:httpx"
    "pydantic:pydantic"
    "rich:rich"
    "csvkit:csvkit"
)

# Install Python packages into Conda environment
install_actions_python() {
    log_step "Installing Python packages for actions..."

    # Ensure Conda is available
    local conda_python=""
    local conda_pip=""
    
    # 1. Try to find existing functional Conda in PATH
    if command_exists conda; then
        local conda_bin=$(dirname "$(command -v conda)")
        if [[ -x "$conda_bin/python3" ]]; then
            conda_python="$conda_bin/python3"
            conda_pip="$conda_bin/pip3"
        elif [[ -x "$conda_bin/python" ]]; then
            conda_python="$conda_bin/python"
            conda_pip="$conda_bin/pip"
        fi
    fi
    
    # 2. If not found in PATH, check standard location
    if [[ -z "$conda_python" ]]; then
        local miniconda_bin="$HOME/miniconda3/bin"
        if [[ -d "$miniconda_bin" ]]; then
             if [[ -x "$miniconda_bin/python3" ]]; then
                 conda_python="$miniconda_bin/python3"
                 conda_pip="$miniconda_bin/pip3"
             elif [[ -x "$miniconda_bin/python" ]]; then
                 conda_python="$miniconda_bin/python"
                 conda_pip="$miniconda_bin/pip"
             fi
        fi
    fi

    # 3. If still not found, reinstall/install
    if [[ -z "$conda_python" ]]; then
        if [[ -d "$HOME/miniconda3" ]]; then
             log_warning "Found Miniconda directory at $HOME/miniconda3 but no usable python binary."
             log_step "Removing incomplete installation..."
             rm -rf "$HOME/miniconda3"
        fi
        
        log_step "Installing Miniconda..."
        if install_conda_if_needed; then
             # Re-check standard location after install
             if [[ -x "$HOME/miniconda3/bin/python3" ]]; then
                 conda_python="$HOME/miniconda3/bin/python3"
                 conda_pip="$HOME/miniconda3/bin/pip3"
             elif [[ -x "$HOME/miniconda3/bin/python" ]]; then
                 conda_python="$HOME/miniconda3/bin/python"
                 conda_pip="$HOME/miniconda3/bin/pip"
             fi
        else
             log_error "Failed to install Conda."
             return 1
        fi
    fi

    # 4. Final verification and pip fallback
    if [[ -z "$conda_python" ]] || [[ ! -x "$conda_python" ]]; then
        log_error "Could not locate python/pip in Conda environment."
        return 1
    fi
    
    # Ensure pip exists
    if [[ -z "$conda_pip" ]] || [[ ! -x "$conda_pip" ]]; then
         local bin_dir=$(dirname "$conda_python")
         if [[ -x "$bin_dir/pip3" ]]; then
             conda_pip="$bin_dir/pip3"
         elif [[ -x "$bin_dir/pip" ]]; then
             conda_pip="$bin_dir/pip"
         else
             log_step "Installing pip into Conda environment..."
             "$conda_python" -m ensurepip --default-pip >/dev/null 2>&1
             if [[ -x "$bin_dir/pip3" ]]; then
                 conda_pip="$bin_dir/pip3"
             elif [[ -x "$bin_dir/pip" ]]; then
                 conda_pip="$bin_dir/pip"
             fi
         fi
    fi
    
    if [[ ! -x "$conda_pip" ]]; then
         log_error "Could not locate pip in Conda environment."
         return 1
    fi

    log_success "Using Conda environment: $conda_python"

    local installed_count=0
    local already_count=0
    local failed_count=0

    log_step "Installing ${#PYTHON_PACKAGES[@]} packages into Conda env..."

    for pkg in "${PYTHON_PACKAGES[@]}"; do
        # Extract package name (without version constraint) for import check
        local pkg_name="${pkg%%[<>=]*}"
        local import_name="$pkg_name"

        # Try to find import name in mapping
        for mapping in "${PYTHON_IMPORT_MAP[@]}"; do
            if [[ "$mapping" == "$pkg_name"* ]]; then
                import_name="${mapping#*:}"
                break
            fi
        done

        # Check if already installed in venv
        if "$conda_python" -c "import $import_name" 2>/dev/null; then
            already_count=$((already_count + 1))
            # echo -e "  ${DIM}$pkg already installed${NC}"
            continue
        fi

        # Try to install
        echo -e "  ${DIM}Installing $pkg...${NC}"
        local install_error
        # Use --no-warn-script-location to avoid warnings about PATH
        # Quote the package spec to handle version specifiers like >=3.1
        if install_error=$("$conda_pip" install "$pkg" --no-warn-script-location 2>&1); then
            installed_count=$((installed_count + 1))
        else
            failed_count=$((failed_count + 1))
            local error_short=$(echo "$install_error" | head -1 | cut -c1-80)
            echo -e "  ${YELLOW}!${NC} Failed: ${error_short}..."
        fi
    done

    echo ""
    if [[ $already_count -gt 0 ]]; then
        log_success "$already_count packages already installed"
    fi
    if [[ $installed_count -gt 0 ]]; then
        log_success "$installed_count packages installed"
    fi
    if [[ $failed_count -gt 0 ]]; then
        log_warning "$failed_count packages failed to install"
    fi
}

# Homebrew package names for actions binaries
# Some tools are not available in Homebrew (use bioconda instead)
BINARIES_DARWIN_BREW=(
    "blast"              # Available via Homebrew
    "hmmer"              # Available via Homebrew
    "open-babel"         # Available via Homebrew
    "pymol"              # Available via Homebrew
    "jq"                 # Available via Homebrew
)

# Packages not in Homebrew, use bioconda
BINARIES_DARWIN_CONDA=(
    "clustalw"
    "muscle"
)

BINARIES_LINUX_APT=(
    "ncbi-blast+"        # Available
    "hmmer"              # Available
    "clustalw"           # Available
    "muscle"             # Available
    "openbabel"          # Available
    "pymol"              # Available
    "jq"                 # Available
)

BINARIES_LINUX_YUM=(
    "ncbi-blast+"        # Available
    "hmmer"              # Available
    "clustalw"           # Available
    "muscle"             # Available
    "openbabel"          # Available
    "pymol"              # Available
    "jq"                 # Available
)

check_conda_tos() {
    # Check if conda ToS needs to be accepted by doing a dry-run
    local check_output
    check_output=$(conda install --dry-run -c conda-forge --name base jq 2>&1)
    
    if echo "$check_output" | grep -q "Terms of Service"; then
        return 1  # ToS not accepted
    fi
    return 0  # ToS accepted or not required
}

prompt_conda_tos() {
    echo ""
    if [[ "$HAS_GUM" == "true" ]]; then
        gum style \
            --foreground "$BRAND_WARNING" \
            --bold \
            "Conda Terms of Service"
        echo ""
        gum style \
            --foreground "$BRAND_DARK" \
            --margin "0 2" \
            "Conda requires accepting the Terms of Service to install packages from bioconda."
        echo ""
        
        if gum confirm --affirmative="Accept ToS" --negative="Skip" "Accept Conda Terms of Service?"; then
            log_step "Accepting Conda Terms of Service..."
            # Accept ToS for all required channels
            conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main 2>/dev/null || true
            conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r 2>/dev/null || true
            conda tos accept 2>/dev/null || true
            log_success "Conda Terms of Service accepted"
            return 0
        else
            log_warning "Skipping conda packages (ToS not accepted)"
            return 1
        fi
    else
        # Fallback for non-gum
        echo -e "${YELLOW}Conda Terms of Service${NC}"
        echo ""
        echo "Conda requires accepting the Terms of Service to install packages."
        echo ""
        read -p "Accept Conda Terms of Service? [y/N] " -n 1 -r
        echo ""
        
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            log_step "Accepting Conda Terms of Service..."
            conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main 2>/dev/null || true
            conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r 2>/dev/null || true
            conda tos accept 2>/dev/null || true
            log_success "Conda Terms of Service accepted"
            return 0
        else
            log_warning "Skipping conda packages (ToS not accepted)"
            return 1
        fi
    fi
}

install_via_conda() {
    local pkgs=("$@")
    local conda_pkgs=""
    for pkg in "${pkgs[@]}"; do
        conda_pkgs="$conda_pkgs $pkg"
    done

    if ! command_exists conda; then
        log_warning "Conda not found. Install Miniconda or Miniforge to get these packages:"
        echo "  conda install -c bioconda$conda_pkgs"
        return 1
    fi

    # Check if ToS needs to be accepted
    if ! check_conda_tos; then
        if ! prompt_conda_tos; then
            return 1
        fi
    fi

    log_step "Installing$conda_pkgs via conda (base environment)..."
    # Install to base environment - no activation needed for CLI tools
    local conda_output
    if conda_output=$(conda install -y -c bioconda -c conda-forge --name base $conda_pkgs 2>&1); then
        return 0
    else
        log_warning "Conda installation failed"
        echo -e "  ${DIM}Manual install: conda install -c bioconda$conda_pkgs${NC}"
        return 1
    fi
}

check_conda_available() {
    if command_exists conda; then
        # Check if bioconda channel is configured
        if conda config --get channels 2>/dev/null | grep -q "bioconda"; then
            return 0
        fi
        # Try adding channel and installing
        return 0
    fi
    return 1
}

install_actions_binaries() {
    log_step "Installing binaries for actions..."
    echo ""
    echo -e "  ${DIM}Binaries: BLAST+, HMMER, ClustalW, MUSCLE, Open Babel, PyMOL, jq${NC}"
    echo ""

    local os=$(get_os)
    local installed_count=0
    local skipped_count=0
    local conda_count=0

    case "$os" in
        darwin)
            if command_exists brew; then
                export HOMEBREW_NO_AUTO_UPDATE=1
            else
                log_step "Homebrew not found. Attempting to install..."
                if ! install_homebrew_if_needed; then
                    log_warning "Could not install Homebrew. Trying conda..."
                    if ! install_conda_if_needed; then
                        log_error "Neither Homebrew nor Miniconda could be installed."
                        return 1
                    fi
                fi
            fi

            if command_exists brew; then
                log_step "Installing via Homebrew (no auto-update)..."

                # First try Homebrew packages
                for pkg in "${BINARIES_DARWIN_BREW[@]}"; do
                    # Check if already installed
                    if brew list --formula "$pkg" 2>/dev/null >/dev/null; then
                        echo -e "  ${DIM}$pkg already installed${NC}"
                        installed_count=$((installed_count + 1))
                        continue
                    fi

                    # Try to install
                    local brew_error
                    if brew_error=$(HOMEBREW_NO_AUTO_UPDATE=1 brew install "$pkg" 2>&1); then
                        installed_count=$((installed_count + 1))
                    else
                        # Check if it's actually already installed
                        if echo "$brew_error" | grep -q "already installed"; then
                            echo -e "  ${DIM}$pkg already installed${NC}"
                            installed_count=$((installed_count + 1))
                        else
                            log_warning "Package $pkg not available via Homebrew, skipping"
                            skipped_count=$((skipped_count + 1))
                        fi
                    fi
                done
            fi

            # Check which conda packages are needed
            local missing_pkgs=()
            for pkg in "${BINARIES_DARWIN_CONDA[@]}"; do
                if ! command_exists "$pkg" 2>/dev/null; then
                    missing_pkgs+=("$pkg")
                else
                    echo -e "  ${DIM}$pkg already installed${NC}"
                    installed_count=$((installed_count + 1))
                fi
            done

            if [[ ${#missing_pkgs[@]} -gt 0 ]]; then
                if command_exists conda; then
                    log_step "Installing ${missing_pkgs[*]} via conda..."
                    if install_via_conda "${missing_pkgs[@]}"; then
                        conda_count=${#missing_pkgs[@]}
                    fi
                else
                    log_step "Installing Miniconda for package management..."
                    if install_conda_if_needed; then
                        export PATH="$HOME/miniconda3/bin:$PATH"
                        if install_via_conda "${missing_pkgs[@]}"; then
                            conda_count=${#missing_pkgs[@]}
                        fi
                    else
                        echo -e "  ${DIM}Install manually: conda install -c bioconda ${missing_pkgs[*]}${NC}"
                    fi
                fi
            fi

            if [[ $installed_count -gt 0 ]]; then
                log_success "$installed_count binaries installed"
            fi
            if [[ $conda_count -gt 0 ]]; then
                log_success "$conda_count binaries installed via conda"
            fi
            ;;
        linux)
            if command_exists apt-get; then
                log_step "Installing via apt..."
                for pkg in "${BINARIES_LINUX_APT[@]}"; do
                    if dpkg -l "$pkg" 2>/dev/null | grep -q "^ii"; then
                        echo -e "  ${DIM}$pkg already installed${NC}"
                        installed_count=$((installed_count + 1))
                    elif sudo apt-get install -y "$pkg" 2>/dev/null; then
                        installed_count=$((installed_count + 1))
                    else
                        log_warning "Package $pkg not available via apt, skipping"
                        skipped_count=$((skipped_count + 1))
                    fi
                done
                if [[ $installed_count -gt 0 ]]; then
                    log_success "$installed_count binaries installed via apt"
                fi
            elif command_exists yum; then
                log_step "Installing via yum..."
                for pkg in "${BINARIES_LINUX_YUM[@]}"; do
                    if rpm -q "$pkg" 2>/dev/null >/dev/null; then
                        echo -e "  ${DIM}$pkg already installed${NC}"
                        installed_count=$((installed_count + 1))
                    elif sudo yum install -y "$pkg" 2>/dev/null; then
                        installed_count=$((installed_count + 1))
                    fi
                done
                if [[ $installed_count -gt 0 ]]; then
                    log_success "$installed_count binaries installed via yum"
                fi
            elif command_exists dnf; then
                log_step "Installing via dnf..."
                for pkg in "${BINARIES_LINUX_YUM[@]}"; do
                    if rpm -q "$pkg" 2>/dev/null >/dev/null; then
                        echo -e "  ${DIM}$pkg already installed${NC}"
                        installed_count=$((installed_count + 1))
                    elif sudo dnf install -y "$pkg" 2>/dev/null; then
                        installed_count=$((installed_count + 1))
                    fi
                done
                if [[ $installed_count -gt 0 ]]; then
                    log_success "$installed_count binaries installed via dnf"
                fi
            else
                log_warning "Package manager not found. Install manually or use conda:"
                echo "  conda install -c bioconda blast hmmer clustalw muscle"
                echo "  conda install -c conda-forge openbabel pymol-open-source jq"
            fi
            ;;
        *)
            log_warning "Unsupported OS for binary installation"
            ;;
    esac
}

install_actions_environment() {
    echo ""
    
    if [[ "$HAS_GUM" == "true" ]]; then
        gum style \
            --foreground "$BRAND_INFO" \
            --bold \
            "Installing Actions Environment"
        gum style \
            --foreground "$BRAND_DARK" \
            --faint \
            "════════════════════════════════════════"
    else
        echo -e "${CYAN}Installing Actions Environment${NC}"
        echo -e "${DIM}════════════════════════════════════════${NC}"
    fi
    echo ""

    local python_path=""
    local pip_path=""
    local conda_path=""
    local conda_root=""
    local homebrew_path=""
    local install_method="unknown"
    local python_version=""

    # Detect Python path
    if command_exists python3; then
        python_path=$(command -v python3)
        python_version=$(python3 --version 2>&1 | awk '{print $2}')
    fi

    # Detect pip path
    if command_exists pip3; then
        pip_path=$(command -v pip3)
    elif command_exists pip; then
        pip_path=$(command -v pip)
    fi

    # Detect conda
    if command_exists conda; then
        conda_path=$(command -v conda)
        conda_root=$(dirname $(dirname $conda_path))
        install_method="conda"
    elif [[ -d "$HOME/miniconda3" ]]; then
        conda_root="$HOME/miniconda3"
        conda_path="$conda_root/bin/conda"
        install_method="conda"
    fi

    # Detect Homebrew
    if command_exists brew; then
        homebrew_path=$(command -v brew)
        if [[ "$install_method" == "unknown" ]]; then
            install_method="brew"
        fi
    fi

    if [[ "$BINARIES_ONLY" == "true" ]]; then
        install_actions_binaries
    elif [[ "$PYTHON_ONLY" == "true" ]]; then
        install_actions_python
    else
        install_actions_python
        install_actions_binaries
    fi

    # Check for Conda environment and prefer it
    # 1. Check standard Miniconda location
    local found_conda=false
    if [[ -d "$HOME/miniconda3/bin" ]]; then
        if [[ -x "$HOME/miniconda3/bin/python3" ]]; then
            python_path="$HOME/miniconda3/bin/python3"
            pip_path="$HOME/miniconda3/bin/pip3"
            found_conda=true
        elif [[ -x "$HOME/miniconda3/bin/python" ]]; then
            python_path="$HOME/miniconda3/bin/python"
            pip_path="$HOME/miniconda3/bin/pip"
            found_conda=true
        fi
        
        if [[ "$found_conda" == "true" ]]; then
            python_version=$("$python_path" --version 2>&1 | awk '{print $2}')
            install_method="conda"
            # Ensure pip path is correct if we guessed
            if [[ ! -x "$pip_path" ]]; then
                 if [[ -x "$(dirname $python_path)/pip3" ]]; then
                     pip_path="$(dirname $python_path)/pip3"
                 elif [[ -x "$(dirname $python_path)/pip" ]]; then
                     pip_path="$(dirname $python_path)/pip"
                 fi
            fi
        fi
    fi
    
    # 2. If not found, check PATH
    if [[ "$found_conda" == "false" ]] && command_exists conda; then
        local conda_bin=$(dirname "$(command -v conda)")
        if [[ -x "$conda_bin/python3" ]]; then
            python_path="$conda_bin/python3"
            pip_path="$conda_bin/pip3"
            found_conda=true
        elif [[ -x "$conda_bin/python" ]]; then
            python_path="$conda_bin/python"
            pip_path="$conda_bin/pip"
            found_conda=true
        fi
        
        if [[ "$found_conda" == "true" ]]; then
            python_version=$("$python_path" --version 2>&1 | awk '{print $2}')
            install_method="conda"
        fi
    fi

    # Generate actions environment config for Go app
    generate_actions_env_config "$python_path" "$pip_path" "$conda_path" "$conda_root" "$homebrew_path" "$install_method" "$python_version"

    echo ""
    echo -e "${GREEN}Actions environment ready!${NC}"
    echo ""
    echo "Available in CLI via the ! trigger (Actions modal)"
    echo ""
}

# Generate actions environment config JSON for the Go app
generate_actions_env_config() {
    local python_path="$1"
    local pip_path="$2"
    local conda_path="$3"
    local conda_root="$4"
    local homebrew_path="$5"
    local install_method="$6"
    local python_version="$7"

    local config_dir="$HOME/.config/ivybloom"
    local config_file="$config_dir/actions_env.json"

    mkdir -p "$config_dir"

    local timestamp
    timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

    # Detect all installed binaries and their paths
    local all_binaries_available=true
    local binary_count=0
    local total_binaries=8

    for cmd in blastp hmmsearch clustalw2 muscle obabel pymol jq csvlook; do
        if command_exists "$cmd"; then
            binary_count=$((binary_count + 1))
        else
            all_binaries_available=false
        fi
    done

    # Check Python packages availability
    local all_packages_available=true
    local package_count=0
    local total_packages=23

    if [[ -n "$python_path" ]] && [[ -x "$python_path" ]]; then
        # Check core packages
        for import_name in numpy pandas scipy matplotlib Bio biotite pyhmmer pyfaidx rdkit py3Dmol sklearn torch transformers prody mdtraj polars pyarrow h5py requests httpx pydantic rich csvkit; do
            if "$python_path" -c "import $import_name" 2>/dev/null; then
                package_count=$((package_count + 1))
            else
                all_packages_available=false
            fi
        done
    else
        all_packages_available=false
    fi

    # Build the config file
    cat > "$config_file" << EOF
{
  "python_path": "$python_path",
  "pip_path": "$pip_path",
  "conda_path": "$conda_path",
  "conda_root": "$conda_root",
  "homebrew_path": "$homebrew_path",
  "installed_at": "$timestamp",
  "install_method": "$install_method",
  "python_version": "$python_version",
  "all_binaries_available": $all_binaries_available,
  "all_packages_available": $all_packages_available,
  "binaries": {
    "blastp": "$(command -v blastp 2>/dev/null || echo "")",
    "hmmsearch": "$(command -v hmmsearch 2>/dev/null || echo "")",
    "clustalw2": "$(command -v clustalw2 2>/dev/null || echo "")",
    "muscle": "$(command -v muscle 2>/dev/null || echo "")",
    "obabel": "$(command -v obabel 2>/dev/null || echo "")",
    "pymol": "$(command -v pymol 2>/dev/null || echo "")",
    "jq": "$(command -v jq 2>/dev/null || echo "")",
    "csvlook": "$(command -v csvlook 2>/dev/null || echo "")"
  }
}
EOF

    chmod 600 "$config_file"
    log_success "Actions environment config saved to $config_file"

    # Also write a .env file for easy sourcing (optional)
    local env_file="$config_dir/actions_env.env"
    cat > "$env_file" << EOF
# IvyBloom Actions Environment Variables
# Generated: $timestamp
export IVYBLOOM_PYTHON_PATH="$python_path"
export IVYBLOOM_PIP_PATH="$pip_path"
export IVYBLOOM_CONDA_PATH="$conda_path"
export IVYBLOOM_CONDA_ROOT="$conda_root"
export IVYBLOOM_HOMEBREW_PATH="$homebrew_path"
export IVYBLOOM_INSTALL_METHOD="$install_method"
export IVYBLOOM_PYTHON_VERSION="$python_version"
export PATH="\$HOME/.local/bin:\$PATH"
EOF

    chmod 600 "$env_file"
}

# ============================================================================
# API Key Management
# ============================================================================

# Add an API key to the actions environment config
add_api_key() {
    local provider="$1"
    local api_key="$2"

    if [[ -z "$provider" ]]; then
        log_error "Provider name is required"
        return 1
    fi

    if [[ -z "$api_key" ]]; then
        log_error "API key is required"
        return 1
    fi

    local config_file="$HOME/.config/ivybloom/actions_env.json"
    local timestamp
    timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

    # Create config directory if needed
    mkdir -p "$(dirname "$config_file")"

    # Create empty config if it doesn't exist
    if [[ ! -f "$config_file" ]]; then
        echo "{}" > "$config_file"
    fi

    # Use jq to add the API key if available, otherwise use sed
    if command_exists jq; then
        local tmp_file
        tmp_file=$(mktemp)

        # Read current config, add API key, write to temp file
        jq --arg provider "$provider" \
           --arg key "$api_key" \
           --arg timestamp "$timestamp" \
           '.api_keys = (if .api_keys == null then {} else .api_keys end) |
            .api_keys[$provider] = {
                "key": $key,
                "provider": $provider,
                "created": $timestamp
            }' "$config_file" > "$tmp_file" 2>/dev/null

        if [[ $? -eq 0 ]]; then
            mv "$tmp_file" "$config_file"
            log_success "Added API key for $provider"
        else
            log_error "Failed to add API key using jq"
            rm -f "$tmp_file"
            return 1
        fi
    else
        # Fallback: use sed to add/update the API key
        log_warning "jq not installed, using sed fallback"

        # Create a temporary Python script for JSON manipulation
        python3 << PYEOF
import json
import sys
import os

config_file = os.path.expanduser("~/.config/ivybloom/actions_env.json")
provider = "$provider"
api_key = "$api_key"
timestamp = "$timestamp"

# Load existing config
try:
    with open(config_file, 'r') as f:
        config = json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
    config = {}

# Initialize api_keys if needed
if 'api_keys' not in config:
    config['api_keys'] = {}

# Add/update the API key
config['api_keys'][provider] = {
    'key': api_key,
    'provider': provider,
    'created': timestamp
}

# Save config
with open(config_file, 'w') as f:
    json.dump(config, f, indent=2)

print(f"Added API key for {provider}")
PYEOF
    fi

    chmod 600 "$config_file"
}

# Remove an API key from the config
remove_api_key() {
    local provider="$1"

    if [[ -z "$provider" ]]; then
        log_error "Provider name is required"
        return 1
    fi

    local config_file="$HOME/.config/ivybloom/actions_env.json"

    if [[ ! -f "$config_file" ]]; then
        log_warning "No config file found"
        return 1
    fi

    if command_exists jq; then
        jq "del(.api_keys[\"$provider\"])" "$config_file" > "${config_file}.tmp" 2>/dev/null
        if [[ $? -eq 0 ]]; then
            mv "${config_file}.tmp" "$config_file"
            log_success "Removed API key for $provider"
        else
            log_error "Failed to remove API key"
            rm -f "${config_file}.tmp"
            return 1
        fi
    else
        python3 << PYEOF
import json
import os

config_file = os.path.expanduser("~/.config/ivybloom/actions_env.json")
provider = "$provider"

try:
    with open(config_file, 'r') as f:
        config = json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
    print("No config file found")
    sys.exit(1)

if 'api_keys' in config and provider in config['api_keys']:
    del config['api_keys'][provider]
    with open(config_file, 'w') as f:
        json.dump(config, f, indent=2)
    print(f"Removed API key for {provider}")
else:
    print(f"No API key found for {provider}")
PYEOF
    fi

    chmod 600 "$config_file"
}

# List all API keys (without showing the actual keys)
list_api_keys() {
    local config_file="$HOME/.config/ivybloom/actions_env.json"

    if [[ ! -f "$config_file" ]]; then
        log_warning "No API keys configured"
        return 0
    fi

    if command_exists jq; then
        jq -r '.api_keys | to_entries[] | "\(.key): \(.value.provider) (created: \(.value.created))"' "$config_file" 2>/dev/null || echo "No API keys found"
    else
        python3 << PYEOF
import json
import os

config_file = os.path.expanduser("~/.config/ivybloom/actions_env.json")

try:
    with open(config_file, 'r') as f:
        config = json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
    print("No API keys configured")
    sys.exit(0)

if 'api_keys' in config:
    for provider, entry in config['api_keys'].items():
        created = entry.get('created', 'unknown')
        print(f"{provider}: {entry.get('provider', provider)} (created: {created})")
else:
    print("No API keys configured")
PYEOF
    fi
}

# ============================================================================
# Main
# ============================================================================

main() {
    # Handle verify-only mode
    if [[ "$VERIFY_ONLY" == "true" ]]; then
        run_verify_only
        exit 0
    fi

    print_banner

    # Skip binary installation if already installed (e.g., via npm or Homebrew)
    if [[ "${IVYBLOOM_SKIP_BINARY:-}" == "1" ]]; then
        log_step "Binary already installed, skipping download"
    else
        # Initialize CDN URL from config endpoint or fallback
        init_cdn_url
        log_step "CDN: $RELEASES_BASE_URL"

        # Determine installation method
        local method="$METHOD"
        if [[ "$method" == "auto" ]]; then
            method=$(detect_best_method)
            log_step "Auto-detected method: $method"
        fi

        # Install
        case "$method" in
            brew)   install_with_brew ;;
            npm)    install_with_npm ;;
            choco)  install_with_choco ;;
            binary) install_binary ;;
            source) install_from_source ;;
            *)      die "Unknown method: $method (use: auto, binary, brew, npm, choco, source)" ;;
        esac

        # Post-install
        setup_path

        if [[ "$VERIFY" == "true" ]]; then
            verify_install || exit 1
        fi
    fi

    # Install actions environment by default (unless skipped)
    if [[ "$SKIP_ACTIONS_ENV" != "true" ]]; then
        install_actions_environment
    fi

    print_next_steps
}

main "$@"
