#!/bin/sh
# Archis uninstaller.
#
#   curl -fsSL https://archis.dev/uninstall.sh | sh
#
# WHAT THIS IS FOR, NOW THAT `chi uninstall` EXISTS. The binary does the whole
# opt-out itself, and it is the half that knows where anything is, so when a `chi`
# is found this script hands the work to it and adds nothing of its own. What it
# still exists for is the machine where the binary is already gone — deleted, or
# never installed by the installer — because the leftovers are then nobody's to
# find but this script's, and a shell script needs nothing installed to run.
#
# WHY THE ORDER IS THE WHOLE DESIGN, in both paths. `chi` is the only thing that
# knows which repositories it indexed, which settings files carry its hook
# entries, which instruction files carry its briefing block. So everything the
# binary can remove is removed *while the binary is still here*, and the binary
# goes last. Delete it first and every `.chi` directory, hook entry and paragraph
# in somebody's CLAUDE.md becomes litter to find by hand.
#
# NONE OF THIS NEEDS A LICENCE. `chi eject` and `chi auth logout` are ungated on
# purpose, so this works on a machine whose trial ended, which is the most likely
# machine to be running it.
#
# It is written in POSIX sh, not bash, for the same reason the installer is: the
# smallest Linux images ship dash as /bin/sh.
#
# Options:
#   --keep-sessions  keep each repository's working history — the one thing
#                    `chi index` cannot rebuild
#   --keep-repos     leave repositories alone entirely: machine setup and binaries only
#   --purge          also delete the state directory: credentials, device identity,
#                    index keys, telemetry spool, repository registry
#   --dry-run        print what would happen and change nothing
#   -y, --yes        do not ask. Required when there is no terminal to ask at
#
# Environment overrides:
#   CHI_INSTALL_DIR  where the binaries were installed
#   CHI_STATE_DIR    where Archis keeps machine state, if it was moved

set -eu

# --- options ------------------------------------------------------------------
KEEP_SESSIONS=0
KEEP_REPOS=0
PURGE=0
DRY_RUN=0
ASSUME_YES=0

while [ $# -gt 0 ]; do
    case "$1" in
        --keep-sessions) KEEP_SESSIONS=1 ;;
        --keep-repos)    KEEP_REPOS=1 ;;
        --purge)         PURGE=1 ;;
        --dry-run)       DRY_RUN=1 ;;
        -y|--yes)        ASSUME_YES=1 ;;
        -h|--help)
            # Written out rather than read back from this file: piped through
            # `sh`, "$0" is the shell and there is no file to read.
            cat <<'HELP'
Archis uninstaller.

  curl -fsSL https://archis.dev/uninstall.sh | sh

Removes Archis from every repository it indexed, then the machine-wide setup, then
the binaries — in that order, because `chi` is what knows where any of it is.

  --keep-sessions  keep each repository's working history, which `chi index`
                   cannot rebuild
  --keep-repos     leave repositories alone: machine setup and binaries only
  --purge          also delete the state directory (credentials, device
                   identity, index keys, telemetry spool, registry)
  --dry-run        print what would happen and change nothing
  -y, --yes        do not ask. Required when there is no terminal to ask at

  CHI_INSTALL_DIR  where the binaries were installed
  CHI_STATE_DIR    where Archis keeps machine state, if it was moved
HELP
            exit 0 ;;
        *)
            printf 'error: unknown option %s (try --help)\n' "$1" >&2
            exit 2 ;;
    esac
    shift
done

# --- output -------------------------------------------------------------------
# Colour only when stdout is a terminal. Piped into a log, escape codes are noise.
if [ -t 1 ]; then
    BOLD=$(printf '\033[1m'); DIM=$(printf '\033[2m')
    RED=$(printf '\033[31m'); GREEN=$(printf '\033[32m')
    YELLOW=$(printf '\033[33m'); RESET=$(printf '\033[0m')
else
    BOLD=''; DIM=''; RED=''; GREEN=''; YELLOW=''; RESET=''
fi

say()  { printf '%s\n' "$*"; }
step() { printf '%s==>%s %s\n' "$BOLD" "$RESET" "$*"; }
warn() { printf '%swarning:%s %s\n' "$YELLOW" "$RESET" "$*" >&2; }
die()  { printf '%serror:%s %s\n' "$RED" "$RESET" "$*" >&2; exit 1; }

# What a step would have done, when --dry-run says not to do it.
would() { printf '    %swould%s %s\n' "$DIM" "$RESET" "$*"; }

# Ask, and mean it.
#
# `curl … | sh` leaves stdin holding the script, so a question has to read the
# terminal directly. When there is no terminal there is nobody to answer, and a
# command that deletes session history must never assume yes on somebody's behalf.
ask() {
    [ "$ASSUME_YES" = 1 ] && return 0
    if [ ! -r /dev/tty ]; then
        die "nothing here can answer a question — re-run with --yes if that is what you meant.
  (Piping this script is fine: curl -fsSL $0 | sh -s -- --yes)"
    fi
    printf '%s [y/N] ' "$1" > /dev/tty
    read -r reply < /dev/tty || reply=''
    case "$reply" in
        y|Y|yes|YES|Yes) return 0 ;;
        *) return 1 ;;
    esac
}

# --- find the binaries --------------------------------------------------------
# The same two defaults the installer picks between, plus whatever is on PATH and
# whatever CHI_INSTALL_DIR says. Every one of them is checked, because a machine
# that was installed to ~/.local/bin and later to /usr/local/bin has two.
candidates=''
add_candidate() {
    [ -n "$1" ] || return 0
    case " $candidates " in
        *" $1 "*) return 0 ;;
    esac
    candidates="$candidates $1"
}

add_candidate "${CHI_INSTALL_DIR:-}"
if command -v chi >/dev/null 2>&1; then
    resolved=$(command -v chi)
    add_candidate "$(dirname "$resolved")"
fi
add_candidate /usr/local/bin
add_candidate "$HOME/.local/bin"

CHI=''
install_dirs=''
for dir in $candidates; do
    found=0
    for bin in chi chi-mcp; do
        [ -f "$dir/$bin" ] && found=1
    done
    [ "$found" = 1 ] || continue
    install_dirs="$install_dirs $dir"
    if [ -z "$CHI" ] && [ -x "$dir/chi" ]; then
        CHI="$dir/chi"
    fi
done

say "${BOLD}Archis uninstaller${RESET}"
say ""

if [ -z "$CHI" ]; then
    warn "no \`chi\` binary found in:$candidates
  Repositories cannot be cleaned without it — it is what knows which ones there are.
  If Archis is installed somewhere else, set CHI_INSTALL_DIR and run this again."
fi

# --- resolve the state directory ---------------------------------------------
# Asked of the binary while there still is one: it is authoritative, it honours
# CHI_STATE_DIR, and it is the only thing that knows where a build put its state.
# The platform defaults are the fallback, and they mirror archis_core::state.
state_dir=''
if [ -n "$CHI" ]; then
    state_dir=$("$CHI" license status --json 2>/dev/null \
        | grep -o '"state_dir":[[:space:]]*"[^"]*"' \
        | head -1 \
        | sed 's/.*"state_dir":[[:space:]]*"//; s/"$//') || state_dir=''
fi
if [ -z "$state_dir" ]; then
    if [ -n "${CHI_STATE_DIR:-}" ]; then
        state_dir="$CHI_STATE_DIR"
    elif [ "$(uname -s)" = "Darwin" ]; then
        state_dir="$HOME/Library/Application Support/chi"
    elif [ -n "${XDG_STATE_HOME:-}" ]; then
        state_dir="$XDG_STATE_HOME/chi"
    else
        state_dir="$HOME/.local/state/chi"
    fi
fi

# --- hand it to the binary, when there is one --------------------------------
# One implementation of the opt-out, not two. `chi uninstall` does every step
# below — repositories, machine setup, sign-out, the binaries, and the state
# directory with --purge — from the inventory of what Archis actually wrote,
# which a shell script cannot read. It also removes the two things this script
# never could: the line the installer added to the shell startup file, and the
# `*.chi-backup` copies Archis made before editing anybody's file.
#
# The plan is printed by the command that will carry it out, and this script's
# own is not printed at all: two plans for one run is how they come to disagree.
# The question stays here, because `curl … | sh` leaves stdin holding the script
# and only this side can read the terminal — so the binary is told `--yes` and
# never asks a second time.
if [ -n "$CHI" ] && "$CHI" uninstall --help >/dev/null 2>&1; then
    args=""
    [ "$KEEP_SESSIONS" = 1 ] && args="$args --keep-sessions"
    [ "$KEEP_REPOS" = 1 ]    && args="$args --keep-repos"
    [ "$PURGE" = 1 ]         && args="$args --purge"

    if [ "$DRY_RUN" = 1 ]; then
        "$CHI" uninstall --dry-run $args
        exit $?
    fi

    "$CHI" uninstall --dry-run $args || exit $?
    say ""
    ask "Go ahead?" || { say "Nothing was removed."; exit 0; }
    say ""
    if "$CHI" uninstall --yes $args; then
        exit 0
    fi
    say ""
    say "${YELLOW}${BOLD}Mostly uninstalled${RESET} — the steps above name what is left."
    exit 1
fi

# --- say what is about to happen ---------------------------------------------
# From here down is the machine whose `chi` is too old to have `uninstall`, or
# gone entirely.
say "This will:"
if [ -n "$CHI" ]; then
    if [ "$KEEP_REPOS" = 1 ]; then
        say "  · leave every repository alone (--keep-repos)"
    elif [ "$KEEP_SESSIONS" = 1 ]; then
        say "  · remove Archis from every repository it indexed, keeping their session history"
    else
        say "  · remove Archis from every repository it indexed — index, settings and session history"
    fi
    say "  · remove the machine-wide setup: hooks, briefing, skill, agent registrations"
    say "  · sign this machine out"
fi
for dir in $install_dirs; do
    say "  · delete the binaries in $dir"
done
if [ "$PURGE" = 1 ]; then
    say "  · delete $state_dir"
else
    say "  ${DIM}· keep $state_dir (--purge deletes it)${RESET}"
fi
say ""

if [ "$DRY_RUN" = 0 ]; then
    ask "Go ahead?" || { say "Nothing was removed."; exit 0; }
    say ""
fi

failures=0

# --- 1. the repositories ------------------------------------------------------
# First, and while the binary is here. `--yes` is passed on because this script
# already asked its own question; a second prompt per repository would be the
# same question forty times.
if [ -n "$CHI" ] && [ "$KEEP_REPOS" = 0 ]; then
    step "Removing Archis from every repository it indexed"
    args="--all --yes"
    [ "$KEEP_SESSIONS" = 1 ] && args="$args --keep-sessions"
    if [ "$DRY_RUN" = 1 ]; then
        would "run: chi eject $args"
        "$CHI" eject --all --dry-run || true
    elif ! "$CHI" eject $args; then
        # Kept going deliberately: a repository on a read-only volume must not
        # strand the rest of the uninstall, and the message above says which.
        warn "one or more repositories were left as they were (see above)"
        failures=$((failures + 1))
    fi
    say ""
fi

# --- 2. the machine-wide setup -----------------------------------------------
if [ -n "$CHI" ]; then
    step "Removing the machine-wide setup"
    if [ "$DRY_RUN" = 1 ]; then
        would "run: chi eject --machine --yes"
        "$CHI" eject --machine --dry-run || true
    elif ! "$CHI" eject --machine --yes; then
        warn "some of the machine-wide setup was left in place (see above)"
        failures=$((failures + 1))
    fi
    say ""
fi

# --- 3. the credentials -------------------------------------------------------
# `chi auth logout` clears the local lease and the refresh token — the keychain entry
# on macOS, the encrypted fallback file elsewhere — and tells the control plane so
# the device stops counting against the account's activations. It clears local
# state even when the server cannot be reached, which is why this is not gated on
# the network.
if [ -n "$CHI" ]; then
    step "Signing this machine out"
    if [ "$DRY_RUN" = 1 ]; then
        would "run: chi auth logout"
    elif ! "$CHI" auth logout >/dev/null 2>&1; then
        warn "sign-out did not complete. Credentials may remain in $state_dir
  and, on macOS, in the keychain entry dev.archis.cli (older installs:
  dev.airsemantics.cli)."
        failures=$((failures + 1))
    else
        say "    signed out"
    fi
    say ""
fi

# --- 4. the binaries ----------------------------------------------------------
if [ -n "$install_dirs" ]; then
    step "Deleting the binaries"
    for dir in $install_dirs; do
        for bin in chi chi-mcp; do
            [ -f "$dir/$bin" ] || continue
            if [ "$DRY_RUN" = 1 ]; then
                would "delete $dir/$bin"
            elif rm -f "$dir/$bin"; then
                say "    deleted      $dir/$bin"
            else
                # No sudo, the same rule the installer follows. A directory this
                # script cannot write to is one somebody else installed into.
                warn "could not delete $dir/$bin — try: sudo rm $dir/$bin"
                failures=$((failures + 1))
            fi
        done
    done
    say ""
fi

# --- 5. the state directory ---------------------------------------------------
step "State directory"
if [ ! -d "$state_dir" ]; then
    say "    $state_dir is not there"
elif [ "$PURGE" = 1 ]; then
    # Worth knowing before it goes: the index keys live here. Any `.chi`
    # directory this run did not remove — a repository the registry never
    # recorded — becomes unreadable rather than merely stale.
    if [ "$DRY_RUN" = 1 ]; then
        would "delete $state_dir"
    elif rm -rf "$state_dir"; then
        say "    deleted      $state_dir"
    else
        warn "could not delete $state_dir"
        failures=$((failures + 1))
    fi
else
    say "    kept         $state_dir"
    say "                 ${DIM}credentials, device identity, index keys, registry${RESET}"
    say "                 ${DIM}delete it with: rm -rf \"$state_dir\"${RESET}"
fi
say ""

# --- report -------------------------------------------------------------------
if [ "$DRY_RUN" = 1 ]; then
    say "${DIM}Nothing was changed (--dry-run).${RESET}"
    exit 0
fi

if [ "$failures" -gt 0 ]; then
    say "${YELLOW}${BOLD}Mostly uninstalled${RESET} — $failures step(s) reported a problem above."
    say ""
    say "Anything left is listed there. A repository Archis never indexed was never in"
    say "the registry: find the rest with"
    say "    ${DIM}find \"\$HOME\" -maxdepth 6 -type d -name .chi${RESET}"
    exit 1
fi

say "${GREEN}${BOLD}Archis is uninstalled.${RESET}"
say ""
say "A repository Archis never indexed was never in its registry, so if you suspect one"
say "was missed:"
say "    ${DIM}find \"\$HOME\" -maxdepth 6 -type d -name .chi${RESET}"
say ""
say "Reinstall any time: ${DIM}curl -fsSL https://archis.dev/install.sh | sh${RESET}"
