#!/usr/bin/env bash
# st-morning (docs/19 v2 + docs/16 + docs/20 escalation): the morning ritual — compose the brief,
# pick the executor model per the user's SETTING + the queue's COMPLEXITY routing, and open a
# terminal with Claude Code already prompted: the session walks the user through every decision
# (PR merges included) as interactive questions.
#
#   st-morning            # auto: open a terminal if a display exists, else print the command
#   st-morning --print    # never open; just compose + print the launch command
#   st-morning --deep [--item ID]  # ESCALATION pass (docs/20): brief contains the items the USER
#                         #   escalated in the standard digest (route=escalate-deep); executor = the
#                         #   deep (high-intelligence) model. Launched by the standard session's
#                         #   end-of-digest confirmation — combined, or per-item with --item.
#
# Settings (~/.config/inferroute/the-steward.json):
#   {"review": {"executor": "auto" | "native" | "sonnet" | "<gate-model>",  # standard digest model
#               "deep_executor": "<cmd>",                              # default: ir anthropic --model opus
#               "morning_launch": true}}
#   auto/native/sonnet → NATIVE Anthropic sonnet (`ir anthropic`, no routing/substitution — intended-Sonnet
#   must not be downgraded by the gate). A <gate-model> setting (e.g. kimi) uses the routed gate on purpose.
#   Routing (docs/20, Henry 2026-06-11): ALL items are triaged in the standard digest — deep is a
#   per-OPTION property (🧠 deep-redesign options escalate on the user's choice). Direct-to-deep
#   happens only when every pending item's RECOMMENDED route is deep-redesign.
set -u
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"; ROOT="$(cd "$HERE/.." && pwd)"
CFG="$HOME/.config/inferroute/the-steward.json"
trap 'printf "\033[?25h" 2>/dev/null' EXIT INT TERM   # always restore the cursor (spinner hides it)

MODE=standard; ONLY_ITEM=""
[ "${1:-}" = --deep ] && { MODE=deep; shift; }
[ "${1:-}" = --work ] && { MODE=work; shift; }
[ "${1:-}" = --item ] && { ONLY_ITEM="${2:-}"; shift 2; }

# ---- friendly loading view (TTY only) ----------------------------------------------------------
# The brief is composed by an LLM editor pass that takes ~a minute. Without a clear "working…" view
# the user sees raw compose logs and assumes it's frozen. Show a header + spinner + plain-English
# status while compose runs; fall back to plain logs when not attached to a terminal. [docs/34]
_steward_header() {
  [ -t 1 ] || return 0
  clear 2>/dev/null || true
  printf '\n  \033[38;5;111m🛡  The Steward\033[0m \033[2m— morning review\033[0m\n\n'
}
_compose_spinner() {   # $1=logfile — runs compose-digest in the background, spins until it exits
  local logf="$1"
  bash "$HERE/compose-digest.sh" >"$logf" 2>&1 &
  local pid=$! i=0 msg="gathering the night's signals"
  local frames=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)
  printf '\033[?25l'
  while kill -0 "$pid" 2>/dev/null; do
    case "$(tail -n1 "$logf" 2>/dev/null)" in
      *bundle:*)                    msg="gathering the night's signals" ;;
      *"attempt 1"*)                msg="the editor is reading through the night" ;;
      *"attempt 2"*|*"attempt 3"*)  msg="refining the brief" ;;
      *spawn*)                      msg="composing your brief — this takes a minute" ;;
    esac
    printf '\r  \033[38;5;111m%s\033[0m  %s\033[K' "${frames[i]}" "$msg"
    i=$(( (i + 1) % ${#frames[@]} ))
    sleep 0.12
  done
  wait "$pid"; local rc=$?
  printf '\r\033[K\033[?25h'
  return $rc
}

# DIGEST EDITOR (docs/20): compose the digest plan if stale (one tool-less editor pass; fail-OPEN —
# without a plan the brief renders the deterministic fallback, everything needs-reading).
if [ "$MODE" = standard ]; then
  _steward_header
  if [ -t 1 ]; then
    _CLOG="$(mktemp)"
    if _compose_spinner "$_CLOG"; then
      printf '  \033[32m✓\033[0m brief ready\n'
    else
      printf '  \033[33m•\033[0m using the deterministic brief (compose hit a snag)\n'
    fi
    rm -f "$_CLOG"
  else
    bash "$HERE/compose-digest.sh" >&2 || echo "[st-morning] compose failed — deterministic fallback brief" >&2
  fi
fi
# --work: the standard WORK session (three-lane clerk, docs/20) — implement the user's chosen
# moderate options; standard executor, never the deep one.

# routing counts: escalated items (user chose deep resolutions, waiting) + all-deep-recommended check
read -r N_ALL N_ESC N_DEEPREC <<< "$(python3 - "$ROOT" <<'PY'
import json, subprocess, sys
out = subprocess.run([sys.executable, sys.argv[1] + "/tools/review/queue.py", "--json"],
                     capture_output=True, text=True)
try: q = json.loads(out.stdout)
except Exception: q = []
def rec_deep(i):
    rec = [o for o in (i.get("options") or []) if o.get("recommended")]
    return bool(rec) and rec[0].get("difficulty") == "deep-redesign"
print(len(q), sum(1 for i in q if i.get("escalated")), sum(1 for i in q if rec_deep(i)))
PY
)"
# Direct-to-deep ONLY when every pending item's RECOMMENDED route is deep-redesign (Henry: triage
# normally otherwise — deep is a per-OPTION property; escalation happens by the user's choice).
if [ "$MODE" = standard ] && [ "${N_ALL:-0}" -gt 0 ] && [ "$N_ALL" = "${N_DEEPREC:-0}" ]; then
  MODE=deep; echo "[st-morning] ALL $N_ALL pending items RECOMMEND deep-redesign → deep executor directly" >&2
fi

# deep/work sessions launch ONE TERMINAL PER TARGET REPO, cwd'd INTO that repo (Henry 2026-06-12):
# the session inherits the repo's CLAUDE.md, conventions, permissions and tooling (deploy pipelines,
# test commands) — the implementation agent works where the work lives. Standard mode stays rooted
# in night-shift (it spans repos and only merges/transcribes/routes).
# one launch unit per PLAN-MERGE GROUP, each with its resolved target repo (queue.py work_groups:
# merged items = one decision = one session; repo = where the diagnosis's root_cause.file exists)
GROUPS_FOR_MODE() {
  # decision-units sharing a TARGET REPO combine into one session (one terminal per repo)
  python3 "$ROOT/tools/review/queue.py" --work-groups "$MODE" 2>/dev/null \
    | python3 -c "
import json, sys
by = {}
for g in json.load(sys.stdin):
    by.setdefault(g['repo'], []).extend(g['ids'])
for repo, ids in by.items():
    print(repo + '\t' + ','.join(sorted(set(ids))))"
}
BRIEF="$(python3 "$ROOT/tools/review/morning_brief.py" --mode "$MODE" ${ONLY_ITEM:+--item "$ONLY_ITEM"})"

DEEP_EXEC="$(python3 -c "
import json
try: print(json.load(open('$CFG')).get('review',{}).get('deep_executor','ir anthropic --model opus'))
except Exception: print('ir anthropic --model opus')")"

if [ "$MODE" = deep ]; then
  read -ra INVOKE <<< "$DEEP_EXEC"; WHY="deep escalation (docs/20): ${N_ESC:-?} escalated item(s)${ONLY_ITEM:+ · only $ONLY_ITEM}"
elif [ "$MODE" = work ]; then
  WORK_EXEC="$(python3 -c "
import json
try: print(json.load(open('$CFG')).get('review',{}).get('work_executor','ir anthropic --model sonnet'))
except Exception: print('ir anthropic --model sonnet')")"
  read -ra INVOKE <<< "$WORK_EXEC"; WHY="work session (three-lane clerk): user-chosen follow-up items${ONLY_ITEM:+ · only $ONLY_ITEM}"
else
  EXEC_SETTING="${ST_EXECUTOR:-$(python3 -c "
import json,sys
try: print(json.load(open('$CFG')).get('review',{}).get('executor','auto'))
except Exception: print('auto')")}"
  # INTENDED-SONNET → NATIVE ANTHROPIC (Henry 2026-06-15). The review is a user-facing DECISION session;
  # if we mean Sonnet, we use `ir anthropic` (plain claude, the user's own Anthropic creds, NO inferroute
  # routing). The routed gate (`ir --model sonnet`) substitutes the model server-side — on 2026-06-15 it
  # downgraded a "sonnet" request to MiniMax-M2.7, whose account was depleted → 402 in the live review.
  # Routing/substitution is right for the autonomous economy lane (kimi), NOT for an intended-Sonnet decision.
  if [ "$EXEC_SETTING" = auto ] || [ "$EXEC_SETTING" = native ] || [ "$EXEC_SETTING" = sonnet ]; then
    INVOKE=(ir anthropic --model sonnet); WHY="native Anthropic sonnet (intended-Sonnet → native, no routing/substitution)"
  else
    INVOKE=(ir --model "$EXEC_SETTING"); WHY="setting: $EXEC_SETTING (routed)"
  fi
fi
# ── Model policy (docs/67): routed by DEFAULT; native ONLY when remote-control is on (it costs more, and
# only native sessions are drivable from the Claude Code app). The substitution risk that forced native is
# avoided by routing to a SPECIFIC canonical model (no generic "sonnet" → server substitution). ──
RC="$(python3 "$ROOT/tools/settings.py" --get model.remote_control 2>/dev/null)"
if [ "$RC" = "True" ]; then                       # remote-control ON → native + --remote-control (drive from the app)
  case " ${INVOKE[*]} " in *" ir anthropic "*) : ;; *) INVOKE=(ir anthropic --model sonnet) ;; esac
  INVOKE+=(--remote-control); WHY="$WHY + remote-control (Claude Code app · native, costs more)"
elif [ "${INVOKE[0]:-} ${INVOKE[1]:-}" = "ir anthropic" ]; then   # default OFF + resolver chose native → ROUTE it
  RMODEL="${ST_REVIEW_MODEL:-$(python3 -c "import json
try: print(json.load(open('$CFG')).get('review',{}).get('routed_model','kimi'))
except Exception: print('kimi')" 2>/dev/null || echo kimi)}"
  INVOKE=(ir --model "$RMODEL"); WHY="routed review model $RMODEL (model policy default — inferroute, no substitution)"
fi
if [ -t 1 ] && [ "$MODE" = standard ]; then
  printf '  \033[2mopening your review — %s item(s) · %s\033[0m\n\n' "${N_ALL:-0}" "${INVOKE[*]}"
  sleep 0.5
else
  echo "[st-morning] mode=$MODE · ${N_ALL:-?} pending (${N_ESC:-0} escalated) · executor: ${INVOKE[*]} ($WHY)" >&2
  echo "[st-morning] brief: $BRIEF" >&2
fi

# Sweep ALL nested-CC markers: gnome-terminal windows inherit the TERMINAL SERVER's env, and if the
# server was ever spawned from inside a CC session it carries CLAUDECODE/CLAUDE_CODE_SESSION_ID/
# AI_AGENT/… — any one of them trips claude's nested guard → silent instant exit 0 (the "empty
# terminal" bug, diagnosed 2026-06-10). Unset dynamically so future markers are covered too.
TUI_MODE="${ST_TUI:-default}"   # review sessions render INLINE by default (alt-screen hides ir's links/hints + scrollback)
DISP="${DISPLAY:-}"
[ -z "$DISP" ] && for x in /tmp/.X11-unix/X*; do [ -e "$x" ] && DISP=":${x##*X}" && break; done

launch() {  # $1=cwd $2=brief $3=title → terminal (or print)
  # IR_LANE= : the interactive review/work/deep session is USER-FACING (you drive it live) — it must NEVER
  # ride the patient economy lane (which can wait/defer). Force standard lane, defending against an
  # inherited IR_LANE=economy. The MODEL is INVOKE's own concern (sonnet/opus). [Henry 2026-06-15]
  local CMD="cd $1 && for v in \$(env | grep -oE '^(CLAUDE[A-Za-z_]*|AI_AGENT)'); do unset \$v; done; IR_LANE= IR_ALLOW_NESTED=1 ${INVOKE[*]} --settings '{\"tui\":\"$TUI_MODE\"}' \"\$(cat $2)\""
  if [ "${PRINT_ONLY:-0}" = 1 ]; then echo "$CMD"; return 0; fi
  # INLINE: run the review in the CURRENT terminal instead of spawning a new one. The web "open review"
  # button opens ONE terminal that runs st-morning inline, so compose progress + the review are visible
  # immediately in that window (no minutes-long invisible compose, no second terminal). Standard mode
  # only — it has a single launch unit. [docs/34: open-review button fix]
  if [ "${ST_MORNING_INLINE:-0}" = 1 ]; then eval "$CMD"; return $?; fi
  if [ -n "$DISP" ] && command -v gnome-terminal >/dev/null; then
    DISPLAY="$DISP" gnome-terminal --title="$3" --geometry=140x38 -- bash -lc "$CMD; exec bash" \
      && echo "[st-morning] terminal opened on $DISP ($3 · cwd $1)" >&2 && return 0
  fi
  echo "[st-morning] no display/terminal — run it yourself:" >&2; echo "$CMD"
}
[ "${1:-}" = --print ] && PRINT_ONLY=1

if [ "$MODE" = standard ]; then
  launch "$ROOT" "$BRIEF" "🌅 The Steward — morning review"
else
  ICON="🧠 DEEP review"; [ "$MODE" = work ] && ICON="🔧 work session"
  WGROUPS="$(GROUPS_FOR_MODE)"
  [ -z "$WGROUPS" ] && { echo "[st-morning] no $MODE items pending" >&2; exit 0; }
  while IFS=$'\t' read -r R IDS; do
    [ -n "$R" ] || continue
    if [ -n "$ONLY_ITEM" ] && ! printf %s "$IDS" | grep -q "$ONLY_ITEM"; then continue; fi
    RB="$(python3 "$ROOT/tools/review/morning_brief.py" --mode "$MODE" --ids "$IDS" --out "/tmp/st-brief-$MODE-$(basename "$R")-$$.md")"
    launch "$R" "$RB" "$ICON — $(basename "$R")"
  done <<< "$WGROUPS"
fi
