# resolve-account-dir.sh — house-account resolver + registry-membership reporter
# Sourced by setup-account.sh and seed-neo4j.sh. Defines `resolve_and_sweep_account_dir`.
#
# Multi-account managed-service model (Task 983): an install may host N accounts
# under data/accounts/ — exactly one carries role:"house", the rest role:"client".
# The registry is the set of dirs holding a parseable account.json. EVERY
# registered account is durable: this resolver never trashes a dir with an
# account.json. It resolves ACCOUNT_DIR/ACCOUNT_ID to the single role:"house"
# account (or the sole account in the pre-migration window, before
# setup-account.sh stamps role:"house").
#
# Sweep is LOG-ONLY this sprint. The destructive `mv` to .trash/ was removed to
# eliminate any chance of deleting a registered account during the multi-account
# rollout; restoring a guarded destructive sweep is tracked as a follow-up task.
# Orphan dirs (no account.json) are reported, never moved.
#
# Invariant: the TypeScript `resolveAccount()` at platform/ui/app/lib/claude-agent.ts
# is READ-ONLY. This bash helper is the SOLE filesystem-mutation surface for the
# account layout, because seed-time is the only safe moment to mutate it.
#
# Contract (inputs via env vars):
#   ACCOUNTS_DIR — {installDir}/data/accounts
#   USERS_FILE   — $HOME/<brand.configDir>/users.json   (read-tolerant; unused for sweep)
#
# Contract (outputs via env vars):
#   ACCOUNT_ID   — resolved house uuid (existing kept, or freshly minted on fresh install)
#   ACCOUNT_DIR  — full path to the live house account dir
#
# Exit:
#   0 on success; non-zero on abort (registry drift: zero-house with >1 candidate,
#   or multiple houses).

# Helper: print the `role` field of the account.json at $1, or empty string when
# absent/unparseable. Trichotomy is unnecessary here — a missing role just means
# "not a house candidate"; corruption discipline lives in the enumeration step.
_account_dir_role() {
  local config="$1/account.json"
  [ -f "$config" ] || { echo ""; return 0; }
  python3 - "$config" <<'PY' 2>/dev/null || echo ""
import json, sys
try:
    cfg = json.load(open(sys.argv[1]))
except Exception:
    print(""); sys.exit(0)
print(cfg.get("role") or "")
PY
}

# House-account resolver. Enumerate registered candidates (dirs with account.json),
# classify by role, resolve the house (or sole-unlabelled), report orphans, and
# emit the registry snapshot. Never mutates the filesystem layout.
resolve_and_sweep_account_dir() {
  mkdir -p "$ACCOUNTS_DIR"

  # Enumerate candidates: direct children with account.json, basenames not starting with '.'
  local candidates=() orphans=()
  local dir base
  for dir in "$ACCOUNTS_DIR"/*/; do
    [ -d "$dir" ] || continue
    base="$(basename "$dir")"
    case "$base" in .*) continue ;; esac
    if [ -f "$dir/account.json" ]; then
      candidates+=("$base")
    else
      orphans+=("$base")
    fi
  done

  local n=${#candidates[@]}

  # Case 0: no candidates — fresh install, mint new UUID.
  # Atomic-mint discipline: refuse to mint if any stub dir (subdir present,
  # account.json missing) exists — a half-provisioned account from a crashed
  # prior install must be operator-repaired, not silently orphaned.
  if [ "$n" -eq 0 ]; then
    if [ "${#orphans[@]}" -gt 0 ]; then
      echo "==> [seed] FAIL phase=mint reason=\"stub-account-dirs present (no account.json): ${orphans[*]}\" — refusing to mint a fresh accountId; remove or repair the stub before re-running install" >&2
      return 1
    fi
    ACCOUNT_ID="$(cat /proc/sys/kernel/random/uuid 2>/dev/null || python3 -c 'import uuid; print(uuid.uuid4())')"
    ACCOUNT_DIR="$ACCOUNTS_DIR/$ACCOUNT_ID"
    echo "==> [seed] op=registry-snapshot houses=0 clients=0"
    return 0
  fi

  # Classify candidates by role.
  local houses=() clients=() uuid role
  for uuid in "${candidates[@]}"; do
    role="$(_account_dir_role "$ACCOUNTS_DIR/$uuid")"
    case "$role" in
      house) houses+=("$uuid") ;;
      *)     clients+=("$uuid") ;;   # client OR unlabelled (legacy)
    esac
  done

  # Pre-action registry snapshot — the count the post-seed census must match.
  echo "==> [seed] op=registry-snapshot houses=${#houses[@]} clients=${#clients[@]}"

  # Report orphans (dirs with no account.json). Log-only — never moved this sprint.
  # Guard the empty-array expansion: under `set -u` (setup-account.sh uses
  # `set -euo pipefail`), bash 3.2 treats "${orphans[@]}" on an empty array as an
  # unbound variable and aborts.
  local ts; ts=$(date -u +%Y%m%dT%H%M%SZ 2>/dev/null || echo unknown)
  if [ "${#orphans[@]}" -gt 0 ]; then
    for uuid in "${orphans[@]}"; do
      echo "==> [seed] op=sweep uuid=${uuid:0:8} registered=false action=skipped-guarded ts=$ts"
    done
  fi

  # Resolve the house.
  if [ "${#houses[@]}" -eq 1 ]; then
    ACCOUNT_ID="${houses[0]}"
    ACCOUNT_DIR="$ACCOUNTS_DIR/$ACCOUNT_ID"
    echo "==> [seed] house-resolved: ${ACCOUNT_ID:0:8}"
    return 0
  fi

  # Pre-migration tolerance: zero labelled house but exactly one account → it is
  # the house (setup-account.sh stamps role:"house" on it).
  if [ "${#houses[@]}" -eq 0 ] && [ "$n" -eq 1 ]; then
    ACCOUNT_ID="${candidates[0]}"
    ACCOUNT_DIR="$ACCOUNTS_DIR/$ACCOUNT_ID"
    echo "==> [seed] house-resolved: ${ACCOUNT_ID:0:8} (sole account, pre-migration)"
    return 0
  fi

  # Genuine drift — loud-fail.
  echo "==> [seed] FAIL phase=resolve-house reason=\"houses=${#houses[@]} total=$n — expected exactly one role:house (or a single unlabelled account)\" — aborting" >&2
  for uuid in "${candidates[@]}"; do echo "    candidate: $uuid role=$(_account_dir_role "$ACCOUNTS_DIR/$uuid")" >&2; done
  return 1
}
