#!/usr/bin/env bash
# orch-down — tear down orch's install state on this machine.
#
# Reverses what `orch up` (and the npm postinstall) put in place:
#   1. Stop extension daemons (~/.orch/extensions/*/daemon.pid) and sweep
#      their synthetic CC subagent JSONLs
#   2. Remove orch hook entries from ~/.claude/settings.json (including
#      legacy orch-{stop,notify}-marker entries from pre-#94 installs)
#   3. Sweep symlinks orch owns (hooks, skills)
#   4. Remove the fleet-prompt cache
#   5. Remove runtime state (registry, send log, subs, claim, plus migration
#      residue under ~/.cache/orch-stop/ for operators on pre-#94 installs)
#
# What it does NOT do:
#   - Uninstall the npm package — that's `npm uninstall -g @agent-ops/orch`
#     (which would re-run preuninstall + remove the binaries; orch-down
#     handles the residue npm uninstall doesn't touch)
#   - Remove runtime deps (tmux, jq) — other tools may use them
#   - Strip injected fleet doctrine from ~/.codex/AGENTS.md / ~/.gemini/GEMINI.md
#     (orch-up doesn't add those automatically; if you added them manually,
#     remove the marker block manually)
#
# Flags:
#   --yes / -y    skip the confirmation prompt
#   --dry-run     show what would be removed without doing it
#   --keep-state  preserve runtime state caches (registry, send log, etc.)
#   --quiet       suppress all output except errors
set -euo pipefail

YES=0
DRY_RUN=0
KEEP_STATE=0
QUIET=0
while [ $# -gt 0 ]; do
    case "$1" in
        --yes|-y) YES=1; shift ;;
        --dry-run) DRY_RUN=1; shift ;;
        --keep-state) KEEP_STATE=1; shift ;;
        --quiet) QUIET=1; shift ;;
        --help|-h) sed -n '2,21p' "$0"; exit 0 ;;
        *) echo "orch-down: unknown flag: $1" >&2; exit 1 ;;
    esac
done

say() { [ $QUIET -eq 0 ] && echo "$@" || true; }
do_rm() {
    # Args: path. Removes if not in dry-run; logs either way.
    local p=$1
    if [ $DRY_RUN -eq 1 ]; then
        say "  would remove: $p"
    else
        rm -rf "$p"
        say "  removed: $p"
    fi
}

BIN_PATH=$(perl -MCwd=realpath -le 'print realpath shift' "$0" 2>/dev/null || readlink -f "$0" 2>/dev/null || echo "$0")
PKG_ROOT=$(cd "$(dirname "$BIN_PATH")/.." && pwd)

say "=== orch-down — package: $PKG_ROOT ==="
[ $DRY_RUN -eq 1 ] && say "(dry-run mode — no changes will be made)"

# ───────────────────────────────────────────────────────────────────
# Show plan + confirm
# ───────────────────────────────────────────────────────────────────
say
say "This will:"
say "  - Stop extension daemons (~/.orch/extensions/*/daemon.pid) and sweep"
say "    their synthetic CC subagent JSONLs"
say "  - Remove orch hook entries from ~/.claude/settings.json (backup first)"
say "  - Remove symlinks under ~/.claude/hooks/ and ~/.claude/skills/"
say "    that point back to this package"
say "  - Delete ~/.cache/orch-fleet-prompt.md"
if [ $KEEP_STATE -eq 0 ]; then
    say "  - Delete runtime state: ~/.cache/orch-registry/,"
    say "    ~/.cache/orch-subs/, ~/.cache/orch-send.log,"
    say "    ~/.cache/orch-operator.json"
    say "  - Delete pre-#94 migration residue: ~/.cache/orch-stop/,"
    say "    ~/.cache/orch-notify/, ~/.cache/orch-nats-bridge.log,"
    say "    ~/.orch/sessions/"
    say "  - Kill orphaned orch-agent-shim processes and delete ~/.cache/orch-shim/"
else
    say "  - Preserve runtime state (--keep-state)"
fi
say
say "This will NOT:"
say "  - Uninstall the npm package (run: npm uninstall -g @agent-ops/orch)"
say "  - Touch hook commands you've added yourself in settings.json"
say "  - Strip fleet doctrine from ~/.codex/AGENTS.md or ~/.gemini/GEMINI.md"
say

if [ $YES -eq 0 ] && [ $DRY_RUN -eq 0 ]; then
    read -r -p "Continue? [y/N] " ans
    case "$ans" in
        y|Y|yes|YES) ;;
        *) echo "aborted"; exit 0 ;;
    esac
    say
fi

# ───────────────────────────────────────────────────────────────────
# 1. Remove orch hooks from settings.json
# ───────────────────────────────────────────────────────────────────
say "[1/5] stopping extension daemons"
EXT_RUN_BASE="$HOME/.orch/extensions"
if [ -d "$EXT_RUN_BASE" ]; then
    for pid_file in "$EXT_RUN_BASE"/*/daemon.pid; do
        [ -f "$pid_file" ] || continue
        ext_name=$(basename "$(dirname "$pid_file")")
        pid=$(cat "$pid_file" 2>/dev/null || true)
        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
            if [ $DRY_RUN -eq 1 ]; then
                say "  would SIGTERM $ext_name (pid=$pid)"
            else
                kill -TERM "$pid" 2>/dev/null || true
                # Wait up to 5s for graceful exit.
                for _ in 1 2 3 4 5; do
                    kill -0 "$pid" 2>/dev/null || break
                    sleep 1
                done
                kill -KILL "$pid" 2>/dev/null || true
                say "  stopped $ext_name (pid=$pid)"
            fi
        else
            say "  $ext_name: stale pid file (pid=$pid not running)"
        fi
        if [ $DRY_RUN -eq 0 ]; then
            rm -f "$pid_file"
        fi
    done
else
    say "  no extension daemons under $EXT_RUN_BASE"
fi

# Belt-and-suspenders sweep: synthetic CC subagent JSONLs that an extension
# daemon left behind (e.g. SIGKILL before it could clean up). Pattern is
# strictly agent-pct*.jsonl — see extensions/claudecode-subagent-panel/
# internal/writer/EncodePane. Real CC subagent files use a-f hex prefixes,
# so the "pct" prefix is unambiguous.
say "  sweeping orphaned synthetic CC subagent JSONLs"
shopt -s nullglob 2>/dev/null || true
for f in "$HOME"/.claude/projects/*/*/subagents/agent-pct*.jsonl; do
    [ -f "$f" ] || continue
    do_rm "$f"
done
say

say "[2/5] removing orch hooks from ~/.claude/settings.json"
SETTINGS="$HOME/.claude/settings.json"
if [ -f "$SETTINGS" ]; then
    if [ $DRY_RUN -eq 0 ]; then
        BACKUP="$SETTINGS.orch-bak.$(date +%Y%m%d-%H%M%S)"
        cp "$SETTINGS" "$BACKUP"
        say "  backup: $BACKUP"
    fi
    # Walk every event type; for each, filter out matcher entries whose
    # hooks reference any orch-owned script (legacy markers + NATS publishers
    # retired in #94, plus current orch-goal-* hooks). If a matcher has mixed
    # hooks (some orch, some not), drop only the orch ones.
    new=$(jq '
        if .hooks then
            .hooks |= with_entries(
                .value |= map(
                    .hooks |= map(select(.command | test("orch-(stop|notify)-marker|orch-nats-publish-|orch-session-jsonl|orch-goal-(session-context|stop-account)") | not))
                ) | map(select(.hooks | length > 0))
            )
            | .hooks |= with_entries(select(.value | length > 0))
            | if (.hooks | length) == 0 then del(.hooks) else . end
        else . end
    ' "$SETTINGS")

    if [ $DRY_RUN -eq 1 ]; then
        say "  would update $SETTINGS (stripped orch hook entries)"
    else
        echo "$new" > "$SETTINGS.tmp" && mv "$SETTINGS.tmp" "$SETTINGS"
        say "  updated $SETTINGS"
    fi
else
    say "  $SETTINGS does not exist; nothing to remove"
fi
say

# ───────────────────────────────────────────────────────────────────
# 2. Sweep package-owned symlinks
# ───────────────────────────────────────────────────────────────────
say "[3/5] removing package-owned symlinks"
if [ $DRY_RUN -eq 0 ] && [ -f "$PKG_ROOT/scripts/preuninstall.js" ] && command -v node >/dev/null 2>&1; then
    node "$PKG_ROOT/scripts/preuninstall.js" 2>&1 | sed 's/^/  /'
else
    # Inline fallback: sweep symlinks under each target dir whose readlink
    # resolves back to $PKG_ROOT.
    sweep_dir() {
        local src=$1 dst=$2
        local entry name target link_target
        [ -d "$src" ] && [ -d "$dst" ] || return 0
        for entry in "$src"/*; do
            [ -e "$entry" ] || continue
            name=$(basename "$entry")
            target="$dst/$name"
            [ -L "$target" ] || continue
            link_target=$(readlink "$target" 2>/dev/null || true)
            case "$link_target" in
                "$PKG_ROOT"/*) do_rm "$target" ;;
            esac
        done
    }
    sweep_dir "$PKG_ROOT/hooks"   "$HOME/.claude/hooks"
    sweep_dir "$PKG_ROOT/skills"  "$HOME/.claude/skills"
fi
say

# ───────────────────────────────────────────────────────────────────
# 3. Remove fleet-prompt cache
# ───────────────────────────────────────────────────────────────────
say "[4/5] removing fleet-prompt cache"
do_rm "$HOME/.cache/orch-fleet-prompt.md"
say

# ───────────────────────────────────────────────────────────────────
# 4. Remove runtime state (unless --keep-state)
# ───────────────────────────────────────────────────────────────────
say "[5/5] removing runtime state"
if [ $KEEP_STATE -eq 1 ]; then
    say "  --keep-state — preserving runtime caches"
else
    do_rm "$HOME/.cache/orch-registry"
    do_rm "$HOME/.cache/orch-subs"
    do_rm "$HOME/.cache/orch-send.log"
    do_rm "$HOME/.cache/orch-operator.json"

    # Migration residue from the legacy fs-marker IPC + NATS bridge retired
    # in #94. Live writers are gone; these caches accumulate on operator
    # machines that ran pre-#94 installs. Sweep them on teardown so re-installs
    # start clean.
    do_rm "$HOME/.cache/orch-stop"
    do_rm "$HOME/.cache/orch-notify"
    do_rm "$HOME/.cache/orch-nats-bridge.log"
    do_rm "$HOME/.orch/sessions"

    # Kill any orphaned orch-agent-shim processes and remove their logs.
    # Shims are normally bound to their pane's shell lifetime; after an
    # abrupt teardown (e.g. tmux kill-session) they can linger. We send
    # SIGTERM and give them 2 s to drain the NATS connection, then SIGKILL.
    #
    # `pkill -x` is portable between Linux (procps-ng) and macOS (BSD).
    # The process name `orch-agent-shim` is 15 chars — under Linux's 15-char
    # /proc/<pid>/comm limit, so `-x` (exact match) is safe everywhere.
    SHIM_LOG_DIR="$HOME/.cache/orch-shim"
    if command -v pkill >/dev/null 2>&1; then
        if [ $DRY_RUN -eq 1 ]; then
            say "  would kill orphaned orch-agent-shim processes (pkill)"
        else
            # pkill returns 0 when at least one process matched, 1 when none.
            # Skip the 2 s drain on the clean-teardown (no-match) path.
            if pkill -TERM -x orch-agent-shim 2>/dev/null; then
                sleep 2
                pkill -KILL -x orch-agent-shim 2>/dev/null || true
                say "  killed orphaned orch-agent-shim processes"
            fi
        fi
    fi
    # Unconditional log-dir cleanup regardless of pkill availability: stale
    # log files are removed even on systems without pkill or when no shims
    # were running.
    do_rm "$SHIM_LOG_DIR"
fi
say

# ───────────────────────────────────────────────────────────────────
# Summary
# ───────────────────────────────────────────────────────────────────
say "orch-down complete."
say
say "Next steps:"
say "  - To remove the npm package itself: npm uninstall -g @agent-ops/orch"
say "  - If you injected fleet doctrine into ~/.codex/AGENTS.md or"
say "    ~/.gemini/GEMINI.md (orch-up doesn't do this automatically), strip"
say "    the marker blocks manually."