#!/bin/sh
# sessionview-autostart - start the user's chosen agent when sessionview opens.
#
#   sessionview-autostart SOCK SESSION      decide, then act (the frame calls this)
#   sessionview-autostart --status [SOCK SESSION]   print the decision, do nothing
#   sessionview-autostart --start NAME [SOCK] [SESSION]
#                                           start NAME now, no question asked
#                                           (the sidebar's + under agents)
#
# Opening sessionview can start the user's chosen agent. It is configurable,
# and it never starts an agent without asking the first time; after that the
# user can change the configuration if they want it to stop.
#
# ONE config key, THREE states (`autostart_agent`, in the template, so there is
# no hidden default):
#
#   "ask"   ASK ONCE, then remember the answer. The SHIPPED value: an installed
#           copy must never spawn an agent on someone's machine unasked, the
#           same rule that ships `remotes: []` so a fresh install cannot reach
#           anyone else's host.
#   ""      never. A "no" is durable and lives in the same file as a "yes".
#   NAME    start that agent in a window of the inner session - the session's
#           OWN first window when the frame just minted the session and that
#           window is still the untouched shell tmux gives it (`--fresh`, and
#           the check is re-made here), a NEW window in every other case.
#           A frame showing an empty tab `0` beside the agent's tab is the
#           case this avoids: the agent opens in that window, not a new one.
#
# Two refusals that are design, not caution:
#   - ALREADY RUNNING -> never a second session for the same seat. Whether one
#     seat may hold two sessions is undecided; a GUI must not mint that.
#   - The agent never takes a window the user is IN. The one window it may take
#     is the one the frame made a second ago and nobody has touched; a shell
#     with a child process, a second window, or a session that already existed
#     all send it to a window of its own.
#
# Fails OPEN, always: no config, no python, no session command, no tty - the
# frame must open regardless. Nothing here is worth a black window.
set -u
SESSIONVIEW_ROOT="${SESSIONVIEW_ROOT:-$(cd "$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")")/.." && pwd)}"
CFG="$SESSIONVIEW_ROOT/bin/sessionview-config"
# The probe (lib/python.sh), never a bare python3: the one Python every tool
# here runs on. It sets nothing when it finds none; fail open.
. "$SESSIONVIEW_ROOT/lib/python.sh"
PY="${SESSIONVIEW_PYTHON:-}"

# The agent to start, read for the session's own directory, so a project's
# .sessionview can name another agent, or "" for none, over the home choice.
# With no session (or no tmux) it is the home layer's answer.
want() {
  _dir=""
  [ -n "${1:-}" ] && [ -n "${2:-}" ] && command -v tmux >/dev/null 2>&1 &&
    _dir="$(tmux -L "$1" display-message -p -t "=$2:" '#{session_path}' 2>/dev/null || true)"
  if [ -n "$_dir" ] && [ -d "$_dir" ]; then
    "$CFG" get autostart_agent --project "$_dir" 2>/dev/null || true
  else
    "$CFG" get autostart_agent 2>/dev/null || true
  fi
}

# Remember the answer in the home layer (`sessionview-config path`) through
# `sessionview-config set`, the one writer: it writes this key and nothing
# else, via a temp file and a rename.
remember() { "$CFG" set autostart_agent "$1"; }

# Is a seat for this agent already live on this machine?
running() {
  command -v session >/dev/null 2>&1 || return 1
  [ -n "$PY" ] || return 1
  session list --json 2>/dev/null | "$PY" -c '
import json, sys
want = sys.argv[1]
try: rows = json.load(sys.stdin)
except Exception: sys.exit(1)
rows = rows.get("sessions", rows) if isinstance(rows, dict) else rows
for r in rows if isinstance(rows, list) else []:
    if isinstance(r, dict) and (r.get("agent") or "") == want:
        print(r.get("name") or r.get("short") or r.get("session_id") or "?"); sys.exit(0)
sys.exit(1)' "$1"
}

# The names the LAUNCHER can actually resolve -- asked of the launcher, not
# worked out here (`session start --list-agents`). A prompt that only asks
# for an agent name leaves the user guessing which names exist, so it offers
# a list to select from. A free-text box asks the user to know something only
# the launcher knows.
#
# Not a local copy of the <agents-dir> rule. That rule is already written in
# two places (session-start's resolve_agent and tmux-sidebar's
# registered_agents); a third copy here is the one that decides
# what a user is OFFERED, so it is the copy that would silently offer a name
# that then fails to start. Empty output, missing command, any error: the
# free-text question below still works, so this fails open like everything
# else in this file.
agent_list() {
  command -v session >/dev/null 2>&1 || return 0
  session start --list-agents 2>/dev/null
}

# Discard every byte waiting to be read on the terminal. ask() calls this once
# the agent list is in and before the question is printed, so a key pressed
# during that wait cannot answer it: a question that a stray key can answer
# is worse than making a fast typist type again. The terminal has already
# echoed those keys, so they stay on
# screen; they are never read. With -icanon and MIN 0 TIME 0 each read returns
# what is queued, or nothing, at once, so dd stops when the queue is empty
# instead of waiting. The saved settings are put back after, or by the trap if
# HUP, INT or TERM lands mid-drain. If stty cannot read or set the terminal,
# nothing is drained and the question is asked anyway.
drain_tty() {
  _tty="$(stty -g </dev/tty 2>/dev/null)" && [ -n "$_tty" ] || return 0
  trap 'stty "$_tty" </dev/tty 2>/dev/null; exit 1' HUP INT TERM
  if stty -icanon min 0 time 0 </dev/tty 2>/dev/null; then
    dd bs=512 count=64 </dev/tty >/dev/null 2>&1
  fi
  stty "$_tty" </dev/tty 2>/dev/null
  trap - HUP INT TERM
  return 0
}

ask() {
  [ -t 0 ] && [ -t 1 ] || return 1        # no tty: stay silent, stay unset
  printf '\n  sessionview can start an agent for you each time it opens.\n\n'

  # Say what we are waiting on BEFORE the launcher is asked. On a cold machine
  # `session start --list-agents` takes 3-4s, and with nothing after the
  # headline the screen looked finished while it waited.
  printf '  looking up agents...\n\n'

  # Numbered menu when we know the names; the typed name stays valid either
  # way, so nothing is lost when the list cannot be had.
  n=0
  names="$(agent_list)"
  drain_tty          # keys typed during that wait go; only later ones are read
  if [ -n "$names" ]; then
    for nm in $names; do
      n=$((n + 1))
      printf '    %d) %s\n' "$n" "$nm"
    done
    printf '    n) no agent -- never ask again\n\n'
    printf '  Which? (number, name, or "n") > '
  else
    printf '  Which agent? (a name, or "no" to never ask again) > '
  fi

  read -r a </dev/tty || return 1

  # A number picks from the list that was just printed. Out of range is not
  # a silent "no": re-ask rather than remember an answer the user did not give.
  case "$a" in
    ''|*[!0-9]*) : ;;
    *) if [ "$n" -gt 0 ] && [ "$a" -ge 1 ] && [ "$a" -le "$n" ]; then
         i=0
         for nm in $names; do
           i=$((i + 1))
           [ "$i" -eq "$a" ] && a="$nm" && break
         done
       else
         printf '  No such choice.\n\n'; return 1
       fi;;
  esac

  case "$a" in
    # A refused write (the settings file not yet moved to its home) is
    # shown, never covered by a "Set:" that did not happen.
    ""|no|No|NO|n|N)
      if why="$(remember "" 2>&1 >/dev/null)"; then printf '  Noted: no agent. Change it any time in %s\n\n' "$("$CFG" path)"
      else printf '  Not saved:\n%s\n\n' "$why"; fi
      return 1;;
    *)
      if why="$(remember "$a" 2>&1 >/dev/null)"; then printf '  Set: %s. Change it any time in %s\n\n' "$a" "$("$CFG" path)"
      else printf '  Not saved (starting %s this once):\n%s\n\n' "$a" "$why"; fi
      echo "$a" >&3; return 0;;
  esac
}

# May the agent take window $3 of session $2 (socket $1)? Yes only if it is
# still the ONE window of that session, holding ONE pane, still running a
# shell. The window is named by ID by the caller that made it seconds ago, so
# this asks tmux about THAT window and never guesses which one is "first".
#
# Deliberately NOT a check for child processes: a prompt command (git status,
# a clock) is a child of the shell for a few milliseconds at a time, and
# testing for one made the placement a coin toss - measured: `pgrep -P` on
# an untouched pane returned a pid on one run and
# nothing on the next. Nothing can have been typed here anyway: no client is
# attached to this session until the frame splits its main pane, below.
takeable_window() {
  _sk="$1"; _ss="$2"; _wid="$3"          # held in names: `set --` below eats $1..$3
  _w="$(tmux -L "$_sk" list-windows -t "=$_ss" -F '#{window_id} #{window_panes}' 2>/dev/null)" || return 1
  [ -n "$_w" ] || return 1
  [ "$(printf '%s\n' "$_w" | wc -l | tr -d ' ')" = "1" ] || return 1   # one window in the session
  set -- $_w
  [ "${1:-}" = "$_wid" ] || return 1                                   # and it is the one we made
  [ "${2:-0}" = "1" ] || return 1                                      # holding one pane
  _c="$(tmux -L "$_sk" display-message -p -t "$_wid" '#{pane_current_command}' 2>/dev/null)"
  case "$_c" in bash|zsh|sh|dash|ksh|fish) return 0 ;; *) return 1 ;; esac
}

# --start NAME [SOCK] [SESSION]: the sidebar's "+" at the bottom of the agents
# list, a way to start an agent on demand. The SAME action as the
# consent path minus the question: never a second seat for a live agent, a
# window of its own, `session start NAME` inside it - so the agent wakes in
# ITS home (`start: directory:` in its own file), not in the sidebar's cwd.
# The window is made CURRENT (no -d): the user pressed +, so they want to see it.
if [ "${1:-}" = "--start" ]; then
  AGENT="${2:-}"; SOCK="${3:-default}"; SESSION="${4:-main}"
  [ -n "$AGENT" ] || { echo "sessionview-autostart --start: no agent named" >&2; exit 2; }
  names="$(agent_list)"
  if [ "$AGENT" = "-" ]; then          # a plain claude, no agent file (the picker's last row;
    names=""                           # start-agent's `empty = "-"` means the same)
    tmux -L "$SOCK" new-window -t "=$SESSION" -n claude "exec session start -" || exit 1
    echo "started a plain claude in its own window"; exit 0
  fi
  if [ -n "$names" ]; then
    ok=""; for nm in $names; do [ "$nm" = "$AGENT" ] && ok=1; done
    [ -n "$ok" ] || { echo "no agent '$AGENT' - registered: $(echo $names)" >&2; exit 1; }
  fi
  if w="$(running "$AGENT")"; then
    echo "$AGENT is already running ($w) - not starting a second"
    exit 0
  fi
  command -v session >/dev/null 2>&1 || { echo "session is not on PATH" >&2; exit 1; }
  command -v tmux >/dev/null 2>&1 || { echo "tmux is not on PATH" >&2; exit 1; }
  tmux -L "$SOCK" new-window -t "=$SESSION" -n "$AGENT" "exec session start $AGENT" || exit 1
  echo "started $AGENT in its own window"
  exit 0
fi

if [ "${1:-}" = "--status" ]; then
  AGENT="$(want "${2:-}" "${3:-}")"
  case "$AGENT" in
    ask) echo "autostart_agent=ask (will ask on next open)";;
    "")  echo "autostart_agent= (off)";;
    *)   if w="$(running "$AGENT")"; then echo "autostart_agent=$AGENT (already running: $w — would attach, not start)"
         else echo "autostart_agent=$AGENT (would start in its own window)"; fi;;
  esac
  exit 0
fi

SOCK="${1:-default}"; SESSION="${2:-main}"; FRESH_WIN=""
[ "${3:-}" = "--fresh-window" ] && FRESH_WIN="${4:-}"
AGENT="$(want "$SOCK" "$SESSION")"

if [ "$AGENT" = "ask" ]; then
  if [ -t 0 ] && [ -t 1 ]; then
    AGENT="$(ask 3>&1 1>/dev/tty 2>/dev/tty || true)"
  else
    exit 0        # nobody to ask: stay unset, ask next time there is a terminal
  fi
fi
[ -n "${AGENT:-}" ] || exit 0
[ "$AGENT" = "ask" ] && exit 0

# Already running: never a second seat for the same agent.
if w="$(running "$AGENT")"; then
  printf '  %s is already running (%s) — not starting a second.\n' "$AGENT" "$w"
  exit 0
fi

command -v session >/dev/null 2>&1 || exit 0
command -v tmux >/dev/null 2>&1 || exit 0

# The agent takes the session's FIRST window when the frame minted the session
# on this launch and nothing has used that window yet - one window named for
# the agent, no empty 0 beside it (a frame showing an empty tab `0` beside
# the agent's tab is what this avoids). NARROW ON PURPOSE. --fresh is
# passed only on the branch where the frame created the inner session, and
# pristine_sole_window re-checks the window right now; everywhere else the
# rule at the top of this file still holds - the agent never takes a window
# the user is in. Falls through to a new window if the respawn fails: an
# agent in the wrong window beats no agent.
if [ -n "$FRESH_WIN" ] && takeable_window "$SOCK" "$SESSION" "$FRESH_WIN"; then
  if tmux -L "$SOCK" respawn-window -k -t "$FRESH_WIN" "exec session start $AGENT" 2>/dev/null; then
    tmux -L "$SOCK" rename-window -t "$FRESH_WIN" "$AGENT" 2>/dev/null
    exit 0
  fi
fi

tmux -L "$SOCK" new-window -d -t "=$SESSION" -n "$AGENT" "exec session start $AGENT" 2>/dev/null || exit 0
exit 0
