#!/usr/bin/env bash
# aisstack-paths — output portable state-root paths for skill bash blocks
# Usage: eval "$(aisstack-paths)"  → sets AISSTACK_STATE_ROOT, PLAN_ROOT, TMP_ROOT
# Or:    aisstack-paths            → prints AISSTACK_STATE_ROOT=... etc.
#        aisstack-paths --explain  → which variable selected the state root, and why
#        aisstack-paths --get NAME → print one raw value (NAME = AISSTACK_STATE_ROOT,
#                                  PLAN_ROOT or TMP_ROOT); exits 1 on failure
#
# Resolves three roots with explicit fallback chains so skills work the same
# whether installed as a Claude Code plugin (CLAUDE_PLUGIN_DATA / CLAUDE_PLANS_DIR
# set), a global ~/.claude/skills/aisstack/ install, or a local checkout under
# CI / container env where HOME may be unset.
#
# Chains:
#   AISSTACK_STATE_ROOT: AISSTACK_STATE_ROOT -> AISSTACK_HOME -> AISSTACK_STATE_DIR
#                      -> CLAUDE_PLUGIN_DATA (only when CLAUDE_PLUGIN_ROOT=*aisstack*)
#                      -> $HOME/.aisstack -> .aisstack
#                      (one rule, owned by bin/aisstack-state-root.sh; set AISSTACK_HOME
#                      to relocate state; AISSTACK_STATE_ROOT is this script's output,
#                      also honored as input; AISSTACK_STATE_DIR is a legacy alias)
#   PLAN_ROOT:         AISSTACK_PLAN_DIR -> CLAUDE_PLANS_DIR -> $HOME/.claude/plans -> .claude/plans
#   TMP_ROOT:          TMPDIR -> TMP -> .aisstack/tmp (and mkdir -p, best-effort)
# Docs: https://github.com/garrytan/gstack/blob/main/docs/state-root.md
#
# Callers guard the eval, because eval "$(f)" succeeds even when f fails:
#   eval "$(aisstack-paths)"; : "${AISSTACK_STATE_ROOT:?aisstack-paths failed; reinstall with ./setup or /aisstack-upgrade}"
# Skill bash blocks use --get instead (#2763): worktree-isolated Claude Code
# sessions refuse any eval/source, but run a plain assignment from a literal
# command, guarded the same way:
#   AISSTACK_STATE_ROOT=$(aisstack-paths --get AISSTACK_STATE_ROOT); : "${AISSTACK_STATE_ROOT:?aisstack-paths failed; reinstall with ./setup or /aisstack-upgrade}"
#
# Output: values are emitted shell-quoted (printf %q) so `eval` round-trips them
# byte-for-byte. This matters on Windows, where $TMP is a backslash path like
# C:\Users\me\AppData\Local\Temp — with a bare `echo`, eval consumes the
# backslashes as escapes and the caller gets C:UsersmeAppDataLocalTemp. A value
# containing a space (C:\Program Files\Temp) is worse: eval word-splits it and
# the variable ends up empty. Quoting here is the only fix that works, because
# the corruption happens during eval, before the caller has anything to quote.
# Callers should still quote expansions ("$AISSTACK_STATE_ROOT") for the same
# reason any path variable needs quoting.
set -u

_AISSTACK_DOC="https://github.com/garrytan/gstack/blob/main/docs/state-root.md"
_AISSTACK_TWIN="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/aisstack-state-root.sh"

# Fail stop: never print a payload that would set an empty AISSTACK_STATE_ROOT.
_aisstack_paths_fail() {
  echo "aisstack-paths: cannot resolve the aisstack state root: $1" >&2
  echo "fix: reinstall aisstack with ./setup (in the aisstack checkout) or /aisstack-upgrade. Docs: $_AISSTACK_DOC" >&2
  exit 1
}
if [ ! -f "$_AISSTACK_TWIN" ] || ! . "$_AISSTACK_TWIN" 2>/dev/null || ! command -v aisstack_state_root_select >/dev/null 2>&1; then
  _aisstack_paths_fail "missing or broken $_AISSTACK_TWIN"
fi

# State root: where aisstack writes projects/, sessions/, analytics/.
aisstack_state_root_select
_state_root="$_aisstack_sr_root"
[ -n "$_state_root" ] || _aisstack_paths_fail "the resolver in $_AISSTACK_TWIN returned an empty root"

if [ "${1:-}" = "--explain" ]; then
  echo "state root: $_state_root (selected by $_aisstack_sr_var)"
  echo "chain (first non-empty wins):"
  _aisstack_sel_seen=0
  for _v in AISSTACK_STATE_ROOT AISSTACK_HOME AISSTACK_STATE_DIR CLAUDE_PLUGIN_DATA; do
    eval "_val=\${$_v:-}"
    if [ -z "$_val" ]; then
      printf '  %-20s unset\n' "$_v"
    elif [ "$_v" = "$_aisstack_sr_var" ]; then
      printf '  %-20s %s  selected\n' "$_v" "$_val"
    elif [ "$_v" = CLAUDE_PLUGIN_DATA ] && [ "$_aisstack_sr_var" = default ]; then
      printf '  %-20s %s  ignored (CLAUDE_PLUGIN_ROOT does not contain "aisstack")\n' "$_v" "$_val"
    else
      printf '  %-20s %s  ignored\n' "$_v" "$_val"
    fi
  done
  _aisstack_user_home
  if [ -n "$_aisstack_home_val" ]; then
    _aisstack_default="$_aisstack_home_val/.aisstack"
  else
    _aisstack_default=".aisstack"
  fi
  if [ "$_aisstack_sr_var" = default ]; then
    printf '  %-20s %s  selected\n' "default" "$_aisstack_default"
  else
    printf '  %-20s %s  ignored\n' "default" "$_aisstack_default"
    _aisstack_has_state=no
    for _e in "$_aisstack_default"/* "$_aisstack_default"/.[!.]*; do
      [ -e "$_e" ] && { _aisstack_has_state=yes; break; }
    done
    echo "default root $_aisstack_default also holds aisstack state: $_aisstack_has_state"
  fi
  echo "merged privacy keys (most restrictive value across roots wins):"
  for _k in telemetry memorable_recall codex_reviews update_check; do
    aisstack_config_select "$_k"
    if [ -n "$_aisstack_cfg_value" ]; then
      printf '  %-17s %s (from %s/config.yaml)\n' "$_k:" "$_aisstack_cfg_value" "$_aisstack_cfg_root"
    else
      printf '  %-17s not set (default applies)\n' "$_k:"
    fi
  done
  echo "docs: $_AISSTACK_DOC"
  exit 0
fi

# Plan root: where /context-save and /codex consult write plan files.
if [ -n "${AISSTACK_PLAN_DIR:-}" ]; then
  _plan_root="$AISSTACK_PLAN_DIR"
elif [ -n "${CLAUDE_PLANS_DIR:-}" ]; then
  _plan_root="$CLAUDE_PLANS_DIR"
elif [ -n "${HOME:-}" ]; then
  _plan_root="$HOME/.claude/plans"
else
  _plan_root=".claude/plans"
fi

# Tmp root: where ephemeral files (codex stderr captures, etc.) live.
# Honor TMPDIR / TMP for Windows + container compat; fall back to a
# project-local .aisstack/tmp so we never write to a system /tmp that may
# be read-only or shared.
if [ -n "${TMPDIR:-}" ]; then
  _tmp_root="$TMPDIR"
elif [ -n "${TMP:-}" ]; then
  _tmp_root="$TMP"
else
  _tmp_root=".aisstack/tmp"
fi
# macOS exports TMPDIR with a trailing slash; mktemp templates built as
# "$TMP_ROOT/name-XXXXXX" would then carry "//", and any consumer comparing
# paths gets a spurious mismatch. Strip it (never strips a bare "/"). #2091
case "$_tmp_root" in
  */) [ "$_tmp_root" != "/" ] && _tmp_root="${_tmp_root%/}" ;;
esac

# Strip any trailing slash so consumers can safely concatenate "$TMP_ROOT/name"
# without producing a double slash. On macOS $TMPDIR ends in `/` by default
# (e.g. /var/folders/.../T/), which would otherwise yield paths like
# `…/T//codex-err-…`. Normalizing at the source means every consumer benefits,
# not just /codex.
_tmp_root="${_tmp_root%/}"
# A value of "/" collapses to "" above; restore it so TMP_ROOT is never empty.
[ -z "$_tmp_root" ] && _tmp_root="/"

# Best-effort mkdir; if it fails (read-only fs, permission denied), the caller
# will discover that on their own write attempt. Don't fail the eval here.
mkdir -p "$_tmp_root" 2>/dev/null || true

if [ "${1:-}" = "--get" ]; then
  case "${2:-}" in
    AISSTACK_STATE_ROOT) printf '%s\n' "$_state_root" ;;
    PLAN_ROOT) printf '%s\n' "$_plan_root" ;;
    TMP_ROOT) printf '%s\n' "$_tmp_root" ;;
    *) echo "usage: aisstack-paths --get AISSTACK_STATE_ROOT|PLAN_ROOT|TMP_ROOT" >&2; exit 2 ;;
  esac
  exit 0
fi

printf 'AISSTACK_STATE_ROOT=%q\n' "$_state_root"
printf 'PLAN_ROOT=%q\n' "$_plan_root"
printf 'TMP_ROOT=%q\n' "$_tmp_root"
