#!/usr/bin/env bash
# ============================================================================
# venv-style activation of the Neo dev environment for an interactive shell.
#
#   source ./activate       (bash / zsh, repo root)
#   source ./activate.fish  (fish)
#   .\activate.ps1          (PowerShell)
#
# Applies the composed pm env (PATH + tool vars) to the CURRENT shell, with
# save/restore: `deactivate` undoes exactly what activation changed.
#
# `neo` becomes a shell function for this checkout. It runs only while the
# shell is inside this worktree and refuses outside it, so a sibling worktree
# keeps its own command. A prompt prefix names the worktree.
#
# Scripts and other processes do not source this: they run under
# `scripts/run-in-neo-env`. Both build on scripts/_activation.sh.
#
# Sync through setup before selecting the environment. PM owns freshness;
# activation does not maintain a second dependency stamp. It trusts the
# recorded tool digest instead of re-hashing every entry. `neo pm
# install` and `neo update` keep that check.
# ============================================================================
_NEO_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Options: `--test-extras a,b` selects the test environment's runtime extras
# (default: [all]); `--` ends options.
# A bare `source ./activate` inherits the caller's positional parameters.
# Ignore unrelated words so scripts with their own arguments can source it.
_neo_test_env="--test-environment"
_neo_expect_extras=""
for _neo_arg in "$@"; do
    if [ -n "$_neo_expect_extras" ]; then
        _neo_test_env="--test-environment=$_neo_arg"
        _neo_expect_extras=""
        continue
    fi
    case "$_neo_arg" in
        --) break ;;
        --test-extras) _neo_expect_extras=1 ;;
        --test-extras=*) _neo_test_env="--test-environment=${_neo_arg#--test-extras=}" ;;
        *) : ;;
    esac
done
if [ -n "$_neo_expect_extras" ]; then
    printf '%s\n' 'activate: --test-extras needs a comma-separated list' >&2
    unset _neo_arg _neo_test_env _neo_expect_extras
    return 2 2>/dev/null || exit 2
fi
unset _neo_arg _neo_expect_extras

# shellcheck source=scripts/_activation.sh
. "$_NEO_REPO/scripts/_activation.sh"

if ! neo_sync "$_NEO_REPO" "$_neo_test_env"; then
    printf '%s\n' 'activate: setup failed; shell environment unchanged' >&2
    unset _neo_test_env
    return 1 2>/dev/null || exit 1
fi
unset _neo_test_env

# Guard against double-sourcing: venv activate precedent (re-activating is a
# no-op re-save; we keep it idempotent by deactivating first). Key on the
# function, not the sentinel: child shells inherit __NEO_ACTIVATED from an
# activated parent but never the function, and there is nothing to undo there.
if [ "$(type -t deactivate 2>/dev/null)" = "function" ]; then
    deactivate
fi

_neo_script="$(neo_compose_env "$_NEO_REPO" sh)" || {
    echo "activate: could not compose the environment (see above)" >&2
    unset _neo_script
    return 1 2>/dev/null || exit 1
}
_neo_keys="$(printf '%s\n' "$_neo_script" | sed -n 's/^export \([A-Za-z_][A-Za-z0-9_]*\)=.*/\1/p' | tr '\n' ' ')"

# --- snapshot what we are about to change (deactivate restores this) ---
# __NEO_ACTIVATED is part of the composed env below (it carries the
# installed-state path this environment was built from), so it is snapshotted
# and exported with every other key — no separate assignment here.
__NEO_SAVED_PATH="$PATH"
for _neo_k in $_neo_keys; do
    eval "__NEO_SAVED_$_neo_k=\"\${$_neo_k+set}\""
    eval "__NEO_PRIOR_$_neo_k=\${$_neo_k-__NEO_UNSET__}"
done
neo_apply_env "$_neo_script"

# This checkout, not whichever `neo` PATH finds. A function beats PATH, an
# alias, and a hashed command, including the MSIX execution alias.
__NEO_WORKTREE="$_NEO_REPO"
# The branch names the worktree. A checkout cannot share a branch with another.
__NEO_WORKTREE_NAME="$(git -C "$_NEO_REPO" rev-parse --abbrev-ref HEAD 2>/dev/null || :)"
if [ -z "$__NEO_WORKTREE_NAME" ] || [ "$__NEO_WORKTREE_NAME" = HEAD ]; then
    __NEO_WORKTREE_NAME="$(basename "$_NEO_REPO")"
fi
__NEO_SAVED_PS1="${PS1-__NEO_UNSET__}"
__NEO_SAVED_PROMPT_COMMAND="${PROMPT_COMMAND-__NEO_UNSET__}"

_neo_worktree_here() {
    _neo_top="$(git rev-parse --show-toplevel 2>/dev/null)" || return 1
    [ "$(cd "$_neo_top" && pwd -P)" = "$(cd "$__NEO_WORKTREE" && pwd -P)" ]
}

neo() {
    _neo_worktree_here || {
        printf '%s\n' "neo: $(pwd) is outside $__NEO_WORKTREE; refusing (the installed command is hidden while this checkout is active)" >&2
        return 1
    }
    (
        cd "$__NEO_WORKTREE" || exit 1
        if [ -n "${PYTHON-}" ]; then
            exec "$PYTHON" neo "$@"
        elif [ -x .venv/Scripts/python.exe ]; then
            exec .venv/Scripts/python.exe neo "$@"
        elif [ -x .venv/bin/python ]; then
            exec .venv/bin/python neo "$@"
        elif [ -x venv/Scripts/python.exe ]; then
            exec venv/Scripts/python.exe neo "$@"
        elif [ -x venv/bin/python ]; then
            exec venv/bin/python neo "$@"
        else
            exec python neo "$@"
        fi
    )
}

_neo_prompt() {
    if [ "$__NEO_SAVED_PS1" = "__NEO_UNSET__" ]; then
        _neo_base_ps1=""
    else
        _neo_base_ps1="$__NEO_SAVED_PS1"
    fi
    if _neo_worktree_here; then
        PS1="($__NEO_WORKTREE_NAME) $_neo_base_ps1"
    else
        PS1="$_neo_base_ps1"
    fi
}

if [ -n "${PROMPT_COMMAND-}" ]; then
    PROMPT_COMMAND="_neo_prompt; ${PROMPT_COMMAND}"
else
    PROMPT_COMMAND=_neo_prompt
fi
_neo_prompt

deactivate() {
    export PATH="$__NEO_SAVED_PATH"
    for _neo_k in $__NEO_KEY_LIST; do
        eval "_neo_was=\"\$__NEO_SAVED_$_neo_k\""
        eval "_neo_old=\"\$__NEO_PRIOR_$_neo_k\""
        if [ "$_neo_was" != "set" ]; then
            unset "$_neo_k"
        else
            export "$_neo_k=$_neo_old"
        fi
        unset "__NEO_SAVED_$_neo_k" "__NEO_PRIOR_$_neo_k"
    done
    if [ "$__NEO_SAVED_PS1" = "__NEO_UNSET__" ]; then
        unset PS1
    else
        PS1="$__NEO_SAVED_PS1"
    fi
    if [ "$__NEO_SAVED_PROMPT_COMMAND" = "__NEO_UNSET__" ]; then
        unset PROMPT_COMMAND
    else
        PROMPT_COMMAND="$__NEO_SAVED_PROMPT_COMMAND"
    fi
    unset __NEO_SAVED_PATH __NEO_KEY_LIST __NEO_ACTIVATED \
          __NEO_WORKTREE __NEO_WORKTREE_NAME \
          __NEO_SAVED_PS1 __NEO_SAVED_PROMPT_COMMAND
    unset -f deactivate neo _neo_worktree_here _neo_prompt
    unset _neo_k _neo_was _neo_old _neo_top
}

# remember the key list for deactivate (kept out of the loop vars above)
__NEO_KEY_LIST="$_neo_keys"

# The composition library is not part of the user's shell.
unset -f neo_sync neo_bootstrap_python neo_compose_env neo_apply_env \
         neo_activation_current neo_ensure_env
unset _NEO_REPO _neo_script _neo_keys _neo_k _neo_top
