#!/bin/sh
# open-agent-window <session> [command...]
# Starts (or reuses) a tmux session and opens ONE window for it (in Terminal or iTerm, the app set in agents-config.lua),
# without taking your focus. The window is opened by Hammerspoon (agentTiler.openB64), whose guard hands focus back
# if that app comes forward. Only when Hammerspoon is unavailable or errors does it open the window itself, unguarded:
# in Terminal by AppleScript, or with AGENT_TILER_APP=iTerm2 through the iTerm Python helper (it2-open-window.py).
# With a command, a shell follows it, so the window stays open and the output stays on screen.
# Exit codes: 0 done, 1 tmux or Hammerspoon failed, 2 Hammerspoon's hs command is not available, 64 bad session name.

S=${1:-}
case "$S" in
  '' | *[!A-Za-z0-9_-]*)
    # A dot or a colon cannot be used: tmux reads them as the window.pane and session:window separators.
    echo "usage: open-agent-window <session> [command...]  (session: letters, digits, _ -; no dots or colons)" >&2
    exit 64
    ;;
  agent-*) ;;
  *) echo "open-agent-window: '$S' does not start with agent-, so the default titlePattern will not tile or colour it." >&2 ;;
esac
shift

# find_bin <name> -> the path on PATH, else under Homebrew (Apple silicon, then Intel).
find_bin() {
  command -v "$1" && return
  for d in /opt/homebrew/bin /usr/local/bin; do
    [ -x "$d/$1" ] && { echo "$d/$1"; return; }
  done
  echo "/opt/homebrew/bin/$1"
}

TMUX_BIN=${TMUX_BIN:-$(find_bin tmux)}
HS_BIN=${HS_BIN:-$(find_bin hs)}
STARTED=${AGENT_TILER_STARTED_OPTION:-@agent_started}
CLOSING=${AGENT_TILER_CLOSING_OPTION:-@agent_closing}
OPENING=${AGENT_TILER_OPENING_OPTION:-@agent_opening}
OPEN_GRACE=${AGENT_TILER_OPEN_GRACE_SECS:-5}

# 0. One call at a time per session (mkdir is atomic). Without this, two calls at once both see "no session" and one
#    fails with "duplicate session", or both ask for a window. A lock whose owner has died is taken over.
LOCK="${TMPDIR:-/tmp}/open-agent-window-$(id -u)-$S.lock"
WAITED=0
until mkdir "$LOCK" 2>/dev/null; do
  OWNER=$(cat "$LOCK/pid" 2>/dev/null)
  if [ -n "$OWNER" ] && ! kill -0 "$OWNER" 2>/dev/null; then rm -rf "$LOCK"; continue; fi
  if [ -z "$OWNER" ] && [ -n "$(find "$LOCK" -maxdepth 0 -mmin +1 2>/dev/null)" ]; then rm -rf "$LOCK"; continue; fi
  WAITED=$((WAITED + 1))
  if [ "$WAITED" -gt 300 ]; then
    echo "open-agent-window: another call for '$S' is stuck (remove $LOCK if no other call is running)." >&2
    exit 1
  fi
  sleep 0.1
done
echo $$ > "$LOCK/pid"
trap 'rm -rf "$LOCK"' EXIT
trap 'exit 1' INT TERM HUP

# 1. The session: reuse it, or start it. With a command, the command is followed by a shell, so the window stays
#    open with the command's output on screen (tmux would otherwise destroy the session when the command ends).
if ! "$TMUX_BIN" has-session -t "=$S" 2>/dev/null; then
  if [ $# -eq 0 ]; then
    "$TMUX_BIN" new-session -d -s "$S" || "$TMUX_BIN" has-session -t "=$S" 2>/dev/null || exit 1
  else
    if [ $# -eq 1 ]; then
      CMD=$1 # one argument is a shell command line, as tmux treats it
    else
      CMD='' # several arguments: each one is passed whole
      for ARG in "$@"; do
        CMD="$CMD '$(printf '%s' "$ARG" | sed "s/'/'\\\\''/g")'"
      done
    fi
    "$TMUX_BIN" new-session -d -s "$S" "$CMD
exec \"\${SHELL:-/bin/sh}\"" || "$TMUX_BIN" has-session -t "=$S" 2>/dev/null || exit 1
  fi
fi

# 2. Never a second window for the same session: one is attached, or one was asked for a moment ago and is still
#    on its way (a window only attaches some hundreds of ms after Hammerspoon starts it). A redundant call stops
#    here and leaves the colours alone.
ATTACHED=$("$TMUX_BIN" display-message -p -t "=$S:" '#{session_attached}' 2>/dev/null)
if [ "${ATTACHED:-0}" -gt 0 ] 2>/dev/null; then
  exit 0
fi
ASKED=$("$TMUX_BIN" show-options -qv -t "=$S:" "$OPENING" 2>/dev/null)
case "$ASKED" in
  '' | *[!0-9]*) ;;
  *) [ $(($(date +%s) - ASKED)) -lt "$OPEN_GRACE" ] && exit 0 ;;
esac

# 3. The window title is the session name, and the colours restart from green.
"$TMUX_BIN" set-option -t "=$S:" set-titles on
"$TMUX_BIN" set-option -t "=$S:" set-titles-string '#S'
"$TMUX_BIN" set-option -t "=$S:" "$STARTED" "$(date +%s)"
"$TMUX_BIN" set-option -u -t "=$S:" "$CLOSING" 2>/dev/null
"$TMUX_BIN" set-option -t "=$S:" "$OPENING" "$(date +%s)"

# 4. Prefer Hammerspoon: it opens the window in the configured app, and its guard hands focus back if that app comes
#    forward (iTerm does, 3 to 7 s after any scripted window; Terminal did not, measured). Base64 avoids every quoting problem.
B64=$(printf '%s' "'$TMUX_BIN' attach -t '=$S'" | base64 | tr -d '\n')
HS_ERR=
if [ -x "$HS_BIN" ]; then
  # </dev/null: hs runs the command, then waits for its input to close, so an open pipe would hang it (found 2026-10-15).
  if OUT=$("$HS_BIN" -t 10 -c "return tostring(agentTiler.openB64('$B64'))" 2>&1 </dev/null); then
    case "$OUT" in *true) exit 0 ;; esac
  fi
  HS_ERR="Hammerspoon could not open the window"
else
  HS_ERR="Hammerspoon's hs command is not available"
fi

# 5. Fallback only. Terminal (the default app): AppleScript "do script", never "activate".
if [ "${AGENT_TILER_APP:-Terminal}" = "Terminal" ]; then
  if "${AGENT_TILER_OSASCRIPT:-/usr/bin/osascript}" -e "tell application \"Terminal\" to do script \"exec /bin/sh -c '$TMUX_BIN attach -t =$S'\"" >/dev/null 2>&1 </dev/null; then
    echo "open-agent-window: $HS_ERR; opened in Terminal by AppleScript, so focus is not guarded on this path." >&2
    exit 0
  fi
  "$TMUX_BIN" set-option -u -t "=$S:" "$OPENING" 2>/dev/null
  echo "open-agent-window: $HS_ERR, and Terminal did not open either. Open Hammerspoon and check the README." >&2
  case "$HS_ERR" in *not\ available*) exit 2 ;; *) exit 1 ;; esac
fi
# iTerm (AGENT_TILER_APP=iTerm2): the Python helper (it2-open-window.py) needs the iterm2 module and iTerm's Python API.
HERE=$(cd "$(dirname "$0")" 2>/dev/null && pwd)
PYENV_PY="$HOME/.hammerspoon/agent-tiler/pyenv/bin/python3"
PY=${AGENT_TILER_PYTHON:-}
[ -z "$PY" ] && [ -x "$PYENV_PY" ] && PY=$PYENV_PY
[ -z "$PY" ] && PY=$(find_bin python3)
IT2=${AGENT_TILER_IT2:-}
for C in "$HERE/it2-open-window.py" "$HOME/.hammerspoon/agent-tiler/it2-open-window.py"; do
  [ -z "$IT2" ] && [ -x "$C" ] && IT2=$C
done
if [ -n "$IT2" ] && [ -x "$IT2" ] && "$PY" "$IT2" --check >/dev/null 2>&1; then
  if "$PY" "$IT2" "'$TMUX_BIN' attach -t '=$S'" >/dev/null 2>&1; then
    echo "open-agent-window: $HS_ERR; opened through the Python helper, so focus is not guarded on this path." >&2
    exit 0
  fi
fi

"$TMUX_BIN" set-option -u -t "=$S:" "$OPENING" 2>/dev/null
echo "open-agent-window: $HS_ERR. Open Hammerspoon and check the README." >&2
case "$HS_ERR" in *not\ available*) exit 2 ;; *) exit 1 ;; esac
