#!/usr/bin/env bash
# Profile-aware delegate launcher template.
#
# Installers may copy this to a user-local `delegate` shim. It routes recognized
# AI_PROFILE shells to matching ~/.delegate/config.<profile>.json files, while
# letting known read-only diagnostics run when the profile config is missing.

_delegate_incoming_config="${DELEGATE_CONFIG-}"
_delegate_profile_config="${HOME}/.delegate/config.${AI_PROFILE:-}.json"

# Resolve an interpreter that can actually run Delegate. The source needs 3.11+
# (`TypeAlias`, `datetime.UTC`), and a bare `python3` resolves to Xcode's 3.9.6
# wherever Homebrew is not first on PATH (cron, a stripped env, some launchers),
# which otherwise dies with a raw ImportError from deep in the tree that never
# names the interpreter. Prints the interpreter; return 1 means none was found,
# return 3 means an explicit DELEGATE_PYTHON was refused and already explained.
_delegate_python() {
  # An explicit override is still validated: pointing it at 3.9 is never
  # intentional, and the failure it produces otherwise is a deep ImportError.
  if [ -n "${DELEGATE_PYTHON:-}" ]; then
    if "$DELEGATE_PYTHON" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 11) else 1)' 2>/dev/null; then
      printf '%s\n' "$DELEGATE_PYTHON"
      return 0
    fi
    printf 'delegate: DELEGATE_PYTHON=%s is not Python 3.11+ (%s)\n' \
      "$DELEGATE_PYTHON" "$("$DELEGATE_PYTHON" -V 2>&1 || echo 'not executable')" >&2
    printf 'delegate: unset it to auto-select, or point it at a 3.11+ interpreter.\n' >&2
    return 3
  fi
  # Homebrew's python3 without naming its prefix: one launcher serves machines
  # with and without Homebrew. brew lives at <prefix>/bin/brew.
  _delegate_brew_python=''
  if [ -n "${HOMEBREW_PREFIX:-}" ]; then
    _delegate_brew_python="$HOMEBREW_PREFIX/bin/python3"
  elif command -v brew >/dev/null 2>&1; then
    _delegate_brew_python="$(dirname "$(dirname "$(command -v brew)")")/bin/python3"
  fi
  for _delegate_candidate in python3 python3.14 python3.13 python3.12 python3.11 \
                             "$_delegate_brew_python" /usr/local/bin/python3; do
    [ -n "$_delegate_candidate" ] || continue
    command -v "$_delegate_candidate" >/dev/null 2>&1 || continue
    if "$_delegate_candidate" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 11) else 1)' 2>/dev/null; then
      printf '%s\n' "$_delegate_candidate"
      return 0
    fi
  done
  return 1
}

# Set _delegate_py once, or explain and exit 2 (the same code bin/delegate.py
# and the package guard use for "wrong Python").
_delegate_require_python() {
  [ -n "${_delegate_py:-}" ] && return 0
  _delegate_py="$(_delegate_python)"
  _delegate_py_rc=$?
  [ "$_delegate_py_rc" -eq 0 ] && return 0
  if [ "$_delegate_py_rc" -ne 3 ]; then
    cat >&2 <<EOF
delegate: no Python 3.11+ interpreter found on PATH.
Delegate needs 3.11 or newer; the python3 here is $(python3 -V 2>&1 || echo 'missing').
Fix: install/expose a newer python3, or set DELEGATE_PYTHON=/path/to/python3.11+.
EOF
  fi
  exit 2
}

_delegate_subcommand() {
  while [ "$#" -gt 0 ]; do
    case "$1" in
      --json|--pass-through|--no-completion-report|--no-mail)
        shift
        ;;
      --cwd|--isolation|--completion-report|--auth-profile|--group|--notify)
        [ "$#" -ge 2 ] || return 0
        shift 2
        ;;
      --help|-h|--version)
        printf '%s\n' "$1"
        return 0
        ;;
      --)
        shift
        break
        ;;
      *)
        printf '%s\n' "$1"
        return 0
        ;;
    esac
  done
}

# Print the global --auth-profile the Python parser would honor, or nothing.
# Mirrors _normalize_global_options in delegate_agent/cli_parser.py: every token
# before `--` is scanned (an --auth-profile after the subcommand is still
# global), flag globals are skipped, value globals are skipped with their value,
# and the last occurrence wins. `--auth-profile=X` is not special-cased because
# the Python walk only matches the two-token spelling. The one approximation:
# cli_parser keeps command-local twins of --group/--completion-report in the
# command stream, which only changes the walk when one of those is immediately
# followed by `--auth-profile` — an argv the command's own parser rejects.
_delegate_flag_profile() {
  _delegate_flag_value=""
  while [ "$#" -gt 0 ]; do
    case "$1" in
      --)
        break
        ;;
      --json|--pass-through|--no-completion-report|--no-mail)
        shift
        ;;
      --auth-profile)
        [ "$#" -ge 2 ] || break
        [ "$2" = "--" ] && break
        _delegate_flag_value="$2"
        shift 2
        ;;
      --cwd|--isolation|--completion-report|--group|--notify)
        [ "$#" -ge 2 ] || break
        [ "$2" = "--" ] && break
        shift 2
        ;;
      *)
        shift
        ;;
    esac
  done
  [ -n "$_delegate_flag_value" ] && printf '%s\n' "$_delegate_flag_value"
  return 0
}

_delegate_launch_help_request() {
  _delegate_launch_cmd="${1:-}"
  [ "$#" -gt 0 ] && shift

  if [ "$_delegate_launch_cmd" = "dry-run" ]; then
    case "${1:-}" in
      --help|-h)
        return 0
        ;;
    esac
    _delegate_launch_cmd="${1:-}"
    [ "$#" -gt 0 ] && shift
  fi

  case "$_delegate_launch_cmd" in
    cursor|codex|kimi|claude|grok|devin|opencode|pi|omp)
      # Parser parity: help is diagnostic only before the mode or immediately
      # after it. Once inline prompt text starts, a later help token is data.
      case "${1:-}" in
        --help|-h)
          return 0
          ;;
        safe|work|call)
          case "${2:-}" in
            --help|-h)
              return 0
              ;;
          esac
          ;;
      esac
      return 1
      ;;
    droid)
      # Droid may place a model alias before the mode, so its last diagnostic
      # help position is one token later than the modeless harnesses.
      case "${1:-}" in
        --help|-h)
          return 0
          ;;
        safe|work|call)
          case "${2:-}" in
            --help|-h)
              return 0
              ;;
          esac
          ;;
        *)
          case "${2:-}" in
            --help|-h)
              return 0
              ;;
            safe|work|call)
              case "${3:-}" in
                --help|-h)
                  return 0
                  ;;
              esac
              ;;
          esac
          ;;
      esac
      return 1
      ;;
    *)
      return 1
      ;;
  esac
}

_delegate_is_readonly_request() {
  while [ "$#" -gt 0 ]; do
    case "$1" in
      --json|--pass-through|--no-completion-report|--no-mail)
        shift
        ;;
      --cwd|--isolation|--completion-report|--auth-profile|--group|--notify)
        [ "$#" -ge 2 ] || return 1
        shift 2
        ;;
      *)
        break
        ;;
    esac
  done
  _cmd="${1:-}"
  [ "$#" -gt 0 ] && shift
  case "$_cmd" in
    cursor|droid|codex|kimi|claude|grok|devin|opencode|pi|omp|dry-run)
      _delegate_launch_help_request "$_cmd" "$@" && return 0
      return 1
      ;;
  esac
  # Non-launch command parsers treat help as diagnostic wherever accepted.
  for _delegate_help_arg in "$@"; do
    case "$_delegate_help_arg" in
      --help|-h)
        return 0
        ;;
    esac
  done
  case "$_cmd" in
    profiles|runs|ps|run-output|describe|snapshot|agent-help|personas|doctor|help|--help|-h|--version)
      return 0
      ;;
    models)
      for _delegate_model_arg in "$@"; do
        if [ "$_delegate_model_arg" = "--live" ]; then
          return 1
        fi
      done
      return 0
      ;;
    capabilities)
      # Scan every remaining arg for a `refresh` positional (not just $1) so
      # `capabilities --verbose refresh` is still classified as a mutation.
      for _delegate_cap_arg in "$@"; do
        if [ "$_delegate_cap_arg" = "refresh" ]; then
          return 1
        fi
      done
      return 0
      ;;
    mail)
      while [ "$#" -gt 0 ]; do
        case "$1" in
          --json)
            shift
            ;;
          --)
            return 1
            ;;
          -*)
            return 1
            ;;
          *)
            break
            ;;
        esac
      done
      case "${1:-}" in
        inbox|status|watch)
          return 0
          ;;
        read)
          for _delegate_mail_arg in "$@"; do
            [ "$_delegate_mail_arg" = "--" ] && break
            [ "$_delegate_mail_arg" = "--peek" ] && return 0
          done
          return 1
          ;;
        *)
          return 1
          ;;
      esac
      ;;
    worktree)
      case "${1:-}" in
        show|list)
          return 0
          ;;
        *)
          return 1
          ;;
      esac
      ;;
    workflow)
      # The parser consumes --json tokens before the action, so the action is
      # the first non-option token after `workflow`; trailing flags/paths
      # never decide classification.
      while [ "$#" -gt 0 ]; do
        case "$1" in
          --json)
            shift
            ;;
          *)
            break
            ;;
        esac
      done
      # Parity with profile_guard.READ_ONLY_WORKFLOW_ACTIONS is enforced by
      # tests; update both together.
      case "${1:-}" in
        check|status|watch|events|result|wait|list)
          return 0
          ;;
        *)
          return 1
          ;;
      esac
      ;;
    *)
      return 1
      ;;
  esac
}

_delegate_profile_fix_message() {
  cat >&2 <<EOF
delegate: AI_PROFILE=${AI_PROFILE} but ${_delegate_profile_config} is missing/unreadable; refusing to run a launch or mutation command on the wrong account.
Fix: create the profile config from config.example.json, or run 'env -u AI_PROFILE delegate config sync-profiles' to materialize missing profile overlays.
Temporary bypass: 'env -u AI_PROFILE delegate ...' uses the base config (work Claude, ambient Codex).
EOF
}

# Precedence, one order for config file, keys, and profile (the table lives in
# docs/configuration.md, "Profile and config precedence"):
#   --auth-profile NAME  >  DELEGATE_CONFIG / AI_PROFILE environment  >  base config.
# A flag naming a profile with an overlay selects that overlay, its keys, and
# AI_PROFILE for the child. Every time the flag overrides or loses to the
# environment, say so on stderr.
_delegate_flag="$(_delegate_flag_profile "$@")"
if [ -n "$_delegate_flag" ]; then
  # Match profiles.resolve_active_profile, including Unicode whitespace.
  _delegate_require_python
  _delegate_flag="$("$_delegate_py" -c 'import sys; print(sys.argv[1].strip())' "$_delegate_flag")" || exit "$?"
fi
case "$_delegate_flag" in
  work|personal)
    _delegate_flag_config="${HOME}/.delegate/config.${_delegate_flag}.json"
    if [ -r "$_delegate_flag_config" ]; then
      if [ -n "$_delegate_incoming_config" ] && [ "$_delegate_incoming_config" != "$_delegate_flag_config" ]; then
        echo "delegate: warning: --auth-profile ${_delegate_flag} selects ${_delegate_flag_config} over DELEGATE_CONFIG=${_delegate_incoming_config} (flag > env). Drop the flag to use that config." >&2
      fi
      if [ -n "${AI_PROFILE:-}" ] && [ "${AI_PROFILE}" != "$_delegate_flag" ]; then
        echo "delegate: warning: --auth-profile ${_delegate_flag} overrides AI_PROFILE=${AI_PROFILE} for this command and its child (flag > env)." >&2
      fi
      export AI_PROFILE="$_delegate_flag"
      _delegate_profile_config="$_delegate_flag_config"
      _delegate_incoming_config=""
    elif [ -n "$_delegate_incoming_config" ] || { [ -n "${AI_PROFILE:-}" ] && [ "${AI_PROFILE}" != "$_delegate_flag" ]; }; then
      echo "delegate: warning: --auth-profile ${_delegate_flag} has no ${_delegate_flag_config}, so the flag selects only the profiles.definitions entry; config and keys stay with ${_delegate_incoming_config:-AI_PROFILE=${AI_PROFILE:-}}. Fix: 'env -u AI_PROFILE delegate config sync-profiles' creates the overlay." >&2
    fi
    ;;
esac

if [ -n "${AI_PROFILE:-}" ]; then
  case "${AI_PROFILE}" in
    personal|work)
      if [ ! -r "$_delegate_profile_config" ]; then
        _delegate_cmd="$(_delegate_subcommand "$@")"
        if _delegate_is_readonly_request "$@"; then
          echo "delegate: warning: AI_PROFILE=${AI_PROFILE} but ${_delegate_profile_config} is missing/unreadable; continuing because '${_delegate_cmd}' is read-only. Launch and mutation commands remain blocked until the profile config exists." >&2
        else
          _delegate_profile_fix_message
          exit 1
        fi
      else
        export DELEGATE_CONFIG="$_delegate_profile_config"
        _delegate_keys="${HOME}/.ai-profiles/${AI_PROFILE}/keys.zsh"
        # ponytail: current keys.zsh files are plain exports; switch to parsing if
        # they ever need zsh-only syntax.
        [ -f "$_delegate_keys" ] && . "$_delegate_keys"
        if [ -n "$_delegate_incoming_config" ]; then
          export DELEGATE_CONFIG="$_delegate_incoming_config"
        fi
      fi
      ;;
    *)
      echo "delegate: AI_PROFILE='${AI_PROFILE}' is not a recognized profile (work|personal); running on the base account" >&2
      ;;
  esac
fi

_delegate_require_python
if [ -n "${DELEGATE_SHIM_PY:-}" ]; then
  exec "$_delegate_py" "$DELEGATE_SHIM_PY" "$@"
fi
exec "$_delegate_py" -m delegate_agent.cli "$@"
