#!/usr/bin/env bash
# exe-start — unified session launcher for exe-os Mode 1
#
# Invoked by thin wrappers ({agent}1, {agent}2, etc.) that pass their $0 as first arg.
#
# Usage (via wrapper):
#   {coo}1          → creates tmux session + boots COO
#   {coo}1 go       → attaches to existing coordinator session
#   {employee}1 go  → attaches to {employee}-{coo}1 session

set -euo pipefail

# Pre-flight: check required commands
command -v tmux >/dev/null 2>&1 || { echo "exe-os requires tmux. Install: brew install tmux (macOS) or apt install tmux (Linux)"; exit 1; }
command -v node >/dev/null 2>&1 || { echo "exe-os requires Node.js. Install: https://nodejs.org/"; exit 1; }

ROSTER_PATH="${HOME}/.exe-os/exe-employees.json"
EXE_START_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"

# True when $1 is the real node-backed exe-os CLI (dist/bin/cli.js) and NOT a
# session wrapper that re-execs exe-start. Wrappers contain the literal string
# "exe-start"; returning one here would cause an infinite exec loop:
#   exe-os -> exe-start -> resolve_exe_os_bin -> exe-os(wrapper) -> exe-start ...
# (bug 2222dc1d). The real CLI never references exe-start.
is_real_exe_os_bin() {
  local candidate="$1"
  [ -n "$candidate" ] && [ -x "$candidate" ] || return 1
  # A wrapper is a tiny shell script that execs exe-start. Reject anything that
  # mentions exe-start so we never loop back into this script.
  if grep -q 'exe-start' "$candidate" 2>/dev/null; then
    return 1
  fi
  return 0
}

resolve_exe_os_bin() {
  # Walk every exe-os on PATH (not just the first) so a shadowing wrapper in
  # ~/.exe-os/bin doesn't mask the real npm-installed CLI behind it.
  local found=""
  while IFS= read -r found; do
    [ -n "$found" ] || continue
    if is_real_exe_os_bin "$found"; then
      printf '%s\n' "$found"
      return 0
    fi
  done <<EOF
$(type -a -p exe-os 2>/dev/null || true)
EOF

  local generated_bin_dir="__EXE_OS_BIN_DIR__"
  if [ "$generated_bin_dir" != "__EXE_OS_BIN_DIR__" ] && is_real_exe_os_bin "$generated_bin_dir/exe-os"; then
    printf '%s\n' "$generated_bin_dir/exe-os"
    return 0
  fi

  if is_real_exe_os_bin "$EXE_START_DIR/exe-os"; then
    printf '%s\n' "$EXE_START_DIR/exe-os"
    return 0
  fi

  return 1
}

# --- Parse wrapper name ---
# Wrappers call: exec ~/.exe-os/bin/exe-start "$0" "$@"
# So $1 = wrapper path, remaining = user args
INVOKED_AS="$(basename "${1:-exe-start}")"
shift # Remove $0 passthrough; remaining args are user's

# Detect runtime suffix: {agent}1-codex -> runtime=codex, strip suffix
RUNTIME="claude"
CLEAN_NAME="$INVOKED_AS"
case "$INVOKED_AS" in
  *-codex)
    RUNTIME="codex"
    CLEAN_NAME="$(echo "$INVOKED_AS" | sed 's/-codex$//')"
    ;;
  *-opencode)
    RUNTIME="opencode"
    CLEAN_NAME="$(echo "$INVOKED_AS" | sed 's/-opencode$//')"
    ;;
esac

# Extract name + number: {agent}1 -> {agent},1
NAME="$(echo "$CLEAN_NAME" | sed 's/[0-9]*$//')"
NUM="$(echo "$CLEAN_NAME" | grep -oE '[0-9]+$' || true)"

if [ -z "$NAME" ]; then
  echo "Could not parse agent name from: $INVOKED_AS"
  echo "  Expected format: {agent}1, {agent}2, {agent}1-codex, etc."
  exit 1
fi

# Default to instance 1 if no number suffix (e.g. invoked as bare name from npm/non-tmux context)
if [ -z "$NUM" ]; then
  NUM="1"
fi

# --- Load COO name from roster ---
if [ ! -f "$ROSTER_PATH" ]; then
  echo "Employee roster not found at $ROSTER_PATH"
  echo "  Run 'exe-os setup' first."
  exit 1
fi

COO_NAME="$(node --input-type=module -e "
  import { readFileSync } from 'node:fs';
  import { createRequire } from 'node:module';
  const require = createRequire(import.meta.url);
  let isCoordinatorRole;
  try {
    // Try npm-installed package first
    const pkg = require.resolve('@askexenow/exe-os/dist/lib/employees.js');
    const mod = await import('file://' + pkg);
    isCoordinatorRole = mod.isCoordinatorRole;
  } catch {
    // Fallback: inline role check
    isCoordinatorRole = (role) => /^(coo|coordinator|chief.of.staff)/i.test(role);
  }
  const r = JSON.parse(readFileSync(process.argv[1], 'utf8'));
  const coo = (Array.isArray(r) ? r : r.employees || []).find(e => isCoordinatorRole(e.role));
  console.log(coo ? coo.name : '');
" "$ROSTER_PATH" 2>/dev/null)"

if [ -z "$COO_NAME" ]; then
  echo "No COO found in roster. Run 'exe-os setup' first."
  exit 1
fi

# --- Mode B: Attach ("go") ---
if [ "${1:-}" = "go" ]; then
  if [ "$NAME" = "$COO_NAME" ]; then
    SESSION="${COO_NAME}${NUM}"
  else
    SESSION="${NAME}-${COO_NAME}${NUM}"
  fi

  if tmux has-session -t "$SESSION" 2>/dev/null; then
    # Keep tmux session env self-identifying for HTTP MCP. This covers older
    # sessions created before env export existed and direct wrapper attach flows.
    if [ "$NAME" = "$COO_NAME" ]; then
      tmux set-environment -t "$SESSION" AGENT_ID "$COO_NAME" 2>/dev/null || true
      tmux set-environment -t "$SESSION" EXE_SESSION_NAME "$SESSION" 2>/dev/null || true
      tmux set-environment -t "$SESSION" EXE_SESSION "$SESSION" 2>/dev/null || true
    else
      ROOT_SESSION="${COO_NAME}${NUM}"
      tmux set-environment -t "$SESSION" AGENT_ID "$NAME" 2>/dev/null || true
      tmux set-environment -t "$SESSION" EXE_SESSION_NAME "$SESSION" 2>/dev/null || true
      tmux set-environment -t "$SESSION" EXE_SESSION "$ROOT_SESSION" 2>/dev/null || true
    fi

    # Detect if already inside the target session — prevent double-attach
    # Only check if we're actually inside tmux (TMUX or TMUX_PANE set).
    # tmux display-message works even outside tmux and returns the last
    # active session, which gives false positives on single-session machines.
    if [ -n "${TMUX:-}" ] || [ -n "${TMUX_PANE:-}" ]; then
      CURRENT_SESSION="$(tmux display-message -p '#{session_name}' 2>/dev/null || true)"
      if [ "$CURRENT_SESSION" = "$SESSION" ]; then
        echo "  Already inside ${SESSION}."
        exit 0
      fi
    fi
    # Unset TMUX to allow attach from auto-tmux shells (common zsh/bash configs).
    # -d detaches other clients to prevent status-bar-in-body rendering bug.
    unset TMUX
    exec tmux attach -d -t "$SESSION"
  else
    if [ "$NAME" = "$COO_NAME" ]; then
      echo "  ${SESSION} is not running. Use ${COO_NAME}${NUM} to start a new session."
    else
      echo "  ${SESSION} is not running. Dispatch work to ${NAME} via /exe inside ${COO_NAME}${NUM}."
    fi
    exit 1
  fi
fi

# --- Standalone CLI passthrough ---
# These subcommands are standalone operations, not session-dependent.
# Forward to `exe-os` binary instead of creating/attaching sessions.
# Fixes bug c79a0335: "exe-os update" intercepted by attach-only logic.
case "${1:-}" in
  update|healthcheck|doctor|setup|support|key|stack-update|install|cloud|settings|version|backup)
    EXE_OS_BIN="$(resolve_exe_os_bin)" || {
      echo "exe-os binary not found. Add the npm global bin directory to PATH or reinstall exe-os."
      exit 127
    }
    exec "$EXE_OS_BIN" "$@"
    ;;
esac

# --- Mode A: Create session (COO only) ---
if [ "$NAME" != "$COO_NAME" ]; then
  echo "  ${INVOKED_AS} is attach-only. Use ${INVOKED_AS} go to connect."
  echo "  Only ${COO_NAME}${NUM} can start new sessions."
  exit 1
fi

# Project folder guard — block $HOME, /, /tmp and /tmp subdirectories
case "$PWD" in
  "$HOME")
    echo "  ${COO_NAME}${NUM} must be run from a project folder, not your home directory."
    echo ""
    echo "  Create or cd into a project folder first:"
    echo "    mkdir ~/my-project && cd ~/my-project"
    echo "    ${COO_NAME}${NUM}"
    exit 1
    ;;
  "/")
    echo "  ${COO_NAME}${NUM} must be run from a project folder, not /."
    echo ""
    echo "  Create or cd into a project folder first:"
    echo "    mkdir ~/my-project && cd ~/my-project"
    echo "    ${COO_NAME}${NUM}"
    exit 1
    ;;
  /tmp|/tmp/*)
    echo "  ${COO_NAME}${NUM} must be run from a project folder, not a temp directory."
    echo ""
    echo "  Create or cd into a project folder first:"
    echo "    mkdir ~/my-project && cd ~/my-project"
    echo "    ${COO_NAME}${NUM}"
    exit 1
    ;;
esac

SESSION="${COO_NAME}${NUM}"

# Session collision guard
if tmux has-session -t "$SESSION" 2>/dev/null; then
  # Find next free N
  NEXT_N=$((NUM + 1))
  while tmux has-session -t "${COO_NAME}${NEXT_N}" 2>/dev/null; do
    NEXT_N=$((NEXT_N + 1))
  done

  # Try to get the working directory of the existing session
  EXISTING_DIR="$(tmux display-message -t "$SESSION" -p '#{pane_current_path}' 2>/dev/null || true)"
  DIR_HINT=""
  if [ -n "$EXISTING_DIR" ]; then
    DIR_HINT=" ($(echo "$EXISTING_DIR" | sed "s|$HOME|~|"))"
  fi

  echo "  ${SESSION} is already running${DIR_HINT}."
  echo ""
  echo "  To reconnect:               ${SESSION} go"
  echo "  To start a new project here: ${COO_NAME}${NEXT_N}"
  exit 1
fi

# Unset TMUX to allow creating sessions from auto-tmux shells
unset TMUX

# Create session and launch COO with full identity + behaviors + bypass permissions
tmux new-session -d -s "$SESSION" -c "$PWD"
# Set session identity in tmux environment — inherited by all processes in this session.
# Critical for HTTP MCP: the daemon needs to know which coordinator session is calling.
tmux set-environment -t "$SESSION" AGENT_ID "$COO_NAME"
tmux set-environment -t "$SESSION" EXE_SESSION_NAME "$SESSION"
tmux set-environment -t "$SESSION" EXE_SESSION "$SESSION"
if [ "$RUNTIME" = "codex" ]; then
  tmux send-keys -t "$SESSION" "export AGENT_ID=$COO_NAME EXE_SESSION_NAME=$SESSION EXE_SESSION=$SESSION && exe-start-codex --agent $COO_NAME --session $SESSION" Enter
elif [ "$RUNTIME" = "opencode" ]; then
  tmux send-keys -t "$SESSION" "export AGENT_ID=$COO_NAME EXE_SESSION_NAME=$SESSION EXE_SESSION=$SESSION && exe-start-opencode --agent $COO_NAME --session $SESSION" Enter
else
  tmux send-keys -t "$SESSION" "export AGENT_ID=$COO_NAME EXE_SESSION_NAME=$SESSION EXE_SESSION=$SESSION && exe-launch-agent --agent $COO_NAME" Enter
fi

# Attach user to session (-d detaches other clients to prevent rendering conflict)
exec tmux attach -d -t "$SESSION"
