#!/bin/bash

###############################################################################
# tmux.sh – Dedicated tmux session management API
# ---------------------------------------------------------------------------
# This script provides a clean API for all tmux session operations.
# It can be sourced by other modules that need tmux functionality.
###############################################################################

# Ensure we have access to core utilities
if ! declare -f repo_root >/dev/null 2>&1 && ! declare -f git_repo_root >/dev/null 2>&1; then
  echo "Error: tmux.sh must be sourced from gw.sh or have core utilities available" >&2
  return 1 2>/dev/null || exit 1
fi

# Use git_repo_root if available, otherwise fallback to repo_root
if declare -f git_repo_root >/dev/null 2>&1; then
  repo_root() { git_repo_root; }
fi

# --------------------------- Tmux Core API ----------------------------------

# Session delimiter (hardcoded)
# Note: tmux session names cannot contain ':'; use a safe delimiter
ORCHESTRA_SESSION_DELIM="__"

# Absolute directory that contains this script when sourced.
_TMUX_API_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"

# Helper: return delimiter
_tmux_delim() { echo "$ORCHESTRA_SESSION_DELIM"; }

# Helper: sanitize tmux session name for filesystem usage
_orchestra_history_key() {
  local key="$1"
  key="${key//\//_}"
  key=$(echo "$key" | tr '[:space:]' '_')
  key=$(echo "$key" | tr -c '[:alnum:]_-' '_')
  while [[ "$key" == *"__"* ]]; do
    key="${key//__/_}"
  done
  key="${key##_}"
  key="${key%%_}"
  echo "$key"
}

_tmux_normalize_app_from_command() {
  local cmd="$1"
  cmd="${cmd#"${cmd%%[![:space:]]*}"}"
  cmd="${cmd%"${cmd##*[![:space:]]}"}"
  if [[ -z "$cmd" ]]; then
    return
  fi

  local tokens=()
  read -r -a tokens <<< "$cmd"
  if [[ ${#tokens[@]} -eq 0 ]]; then
    return
  fi

  local i=0
  while [[ $i -lt ${#tokens[@]} ]]; do
    local t="${tokens[$i]}"
    if [[ "$t" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
      ((i++))
      continue
    fi
    case "$t" in
      sudo|env|command|nohup|time)
        ((i++))
        continue
        ;;
    esac
    break
  done

  if [[ $i -ge ${#tokens[@]} ]]; then
    return
  fi

  local base="${tokens[$i]}"
  base="${base##*/}"
  base="$(printf '%s' "$base" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9._:-' '_')"
  base="${base//__/_}"
  base="${base##_}"
  base="${base%%_}"

  case "$base" in
    python3|python3.*)
      base="python"
      ;;
    pip3|pip3.*)
      base="pip"
      ;;
    docker-compose|docker_compose)
      base="docker_compose"
      ;;
    nvim|nvim.app)
      base="nvim"
      ;;
  esac

  case "$base" in
    ""|bash|zsh|sh|fish|tmux|login|sudo|man|less|more|cat|tail|watch|source|export|set|alias|unalias|history|bindkey)
      return
      ;;
  esac

  printf '%s' "$base"
}

_tmux_truthy() {
  case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
    1|yes|true|on)
      return 0
      ;;
  esac
  return 1
}

_tmux_is_known_tui_app() {
  local app
  app="$(_tmux_normalize_app_from_command "$1")"
  case "$app" in
    opencode|claude|vim|nvim|vi|lazygit|gitui|tig|top|htop|btop|k9s|fzf|yazi|ranger|nnn|less|man)
      return 0
      ;;
  esac
  return 1
}

_tmux_is_tui_context() {
  local pane_cmd="$1"
  local alternate_on="$2"
  local mouse_any_flag="$3"
  local pane_mode="$4"
  local window_name="$5"
  local pane_title="$6"

  if _tmux_truthy "$alternate_on" || _tmux_truthy "$mouse_any_flag"; then
    return 0
  fi

  if [[ -n "$pane_mode" ]]; then
    return 0
  fi

  if _tmux_is_known_tui_app "$pane_cmd"; then
    return 0
  fi

  local combined
  combined="$(printf '%s %s' "$window_name" "$pane_title" | tr '[:upper:]' '[:lower:]')"
  case "$combined" in
    *opencode*|*claude*|*vim*|*nvim*|*lazygit*|*gitui*|*tig*|*k9s*|*fzf*|*yazi*|*ranger*|*nnn*)
      return 0
      ;;
  esac

  return 1
}

# Helper: absolute path to the command hook script (if present)
_orchestra_command_hook() {
  local hook=""

  # 1) Explicit install root set by wrappers (Homebrew/NPM)
  if [[ -n "${GW_ORCHESTRATOR_ROOT-}" ]]; then
    hook="$GW_ORCHESTRATOR_ROOT/shell/orchestra-command-hook.sh"
    if [[ -f "$hook" ]]; then
      echo "$hook"
      return
    fi
  fi

  # 2) Resolve relative to this script's install location
  local script_root
  script_root="$(cd "$_TMUX_API_DIR/.." && pwd -P)"
  hook="$script_root/shell/orchestra-command-hook.sh"
  if [[ -f "$hook" ]]; then
    echo "$hook"
    return
  fi

  # 3) Fallback to repo root for legacy/dev flows
  local root
  root="$(repo_root)"
  if [[ -n "$root" ]]; then
    hook="$root/shell/orchestra-command-hook.sh"
    if [[ -f "$hook" ]]; then
      echo "$hook"
      return
    fi
  fi

  echo ""
}

_orchestra_bridge_script() {
  echo "$(dirname "$_TMUX_API_DIR")/gw-bridge.sh"
}

# Source the command hook inside a tmux session to enable command history logging
_tmux_source_command_hook() {
  local session="$1"
  local hook
  hook="$(_orchestra_command_hook)"
  if [[ -z "$hook" ]]; then
    return
  fi
  if ! tmux_session_exists "$session"; then
    return
  fi
  local bridge
  bridge="$(_orchestra_bridge_script)"
  local panes
  panes=$(tmux list-panes -t "$session" -F '#{pane_id} #{pane_current_command}' 2>/dev/null || echo "")
  if [[ -z "$panes" ]]; then
    return
  fi
  while IFS= read -r line; do
    [[ -z "$line" ]] && continue
    local pane_id pane_cmd
    pane_id="${line%% *}"
    pane_cmd="${line#* }"
    case "$pane_cmd" in
      bash|zsh)
        tmux send-keys -t "$pane_id" "export ORCHESTRA_BRIDGE_PATH='$bridge'; . '$hook'" C-m 2>/dev/null || true
        ;;
      *)
        ;;
    esac
  done <<< "$panes"
}

# Helper: orchestra prefix including delimiter
_tmux_orch_prefix() { printf "orchestra%s" "$( _tmux_delim )"; }

_tmux_session_registry_path() {
  if [[ -n "${ORCHESTRA_SESSION_REGISTRY_PATH-}" ]]; then
    printf '%s\n' "$ORCHESTRA_SESSION_REGISTRY_PATH"
    return
  fi
  printf '%s\n' "$HOME/.orchestra/orchestra.sqlite3"
}

_tmux_shared_root_for_path() {
  local path="$1"
  local old_pwd="$PWD"
  local root=""
  if [[ -n "$path" && -d "$path" ]]; then
    cd "$path" 2>/dev/null || return 1
    root="$(git_shared_root 2>/dev/null || git_repo_root 2>/dev/null || true)"
    cd "$old_pwd" 2>/dev/null || true
  fi
  [[ -n "$root" ]] || return 1
  printf '%s\n' "$root"
}

_tmux_branch_for_path() {
  local path="$1"
  local old_pwd="$PWD"
  local branch=""
  if [[ -n "$path" && -d "$path" ]]; then
    cd "$path" 2>/dev/null || return 1
    branch="$(git_current_branch 2>/dev/null || true)"
    cd "$old_pwd" 2>/dev/null || true
  fi
  [[ -n "$branch" ]] || return 1
  printf '%s\n' "$branch"
}

_tmux_registry_upsert_session() {
  local repo_root="$1"
  local branch="$2"
  local worktree_path="$3"
  local tmux_name="$4"
  local db_path
  db_path="$(_tmux_session_registry_path)"

  [[ -n "$repo_root" && -n "$branch" && -n "$worktree_path" && -n "$tmux_name" ]] || return 1
  have_cmd python3 || return 1

  python3 - "$db_path" "$repo_root" "$branch" "$worktree_path" "$tmux_name" <<'PY'
import os
import sqlite3
import sys
import time

db_path, repo_root, branch, worktree_path, tmux_name = sys.argv[1:6]
os.makedirs(os.path.dirname(db_path), exist_ok=True)
slug = branch.replace("/", "-")
now = int(time.time())

conn = sqlite3.connect(db_path)
conn.execute(
    """
    CREATE TABLE IF NOT EXISTS sessions (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        tmux_name TEXT NOT NULL UNIQUE,
        repo_root TEXT NOT NULL,
        worktree_path TEXT NOT NULL,
        branch TEXT NOT NULL,
        slug TEXT NOT NULL,
        display_name TEXT,
        created_at INTEGER NOT NULL,
        updated_at INTEGER NOT NULL,
        last_seen_at INTEGER NOT NULL
    )
    """
)
conn.execute(
    "CREATE INDEX IF NOT EXISTS idx_sessions_worktree ON sessions(repo_root, worktree_path)"
)
conn.execute("CREATE INDEX IF NOT EXISTS idx_sessions_slug ON sessions(repo_root, slug)")
conn.execute(
    """
    INSERT INTO sessions (
        tmux_name, repo_root, worktree_path, branch, slug,
        created_at, updated_at, last_seen_at
    ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?6, ?6)
    ON CONFLICT(tmux_name) DO UPDATE SET
        repo_root = excluded.repo_root,
        worktree_path = excluded.worktree_path,
        branch = excluded.branch,
        slug = excluded.slug,
        updated_at = excluded.updated_at,
        last_seen_at = excluded.last_seen_at
    """,
    (tmux_name, repo_root, worktree_path, branch, slug, now),
)
conn.commit()
PY
}

_tmux_registry_rename_session() {
  local old_tmux_name="$1"
  local new_tmux_name="$2"
  local display_name="${3:-}"
  local db_path
  db_path="$(_tmux_session_registry_path)"

  [[ -f "$db_path" ]] || return 0
  have_cmd python3 || return 1

  python3 - "$db_path" "$old_tmux_name" "$new_tmux_name" "$display_name" <<'PY'
import sqlite3
import sys
import time

db_path, old_tmux_name, new_tmux_name, display_name = sys.argv[1:5]
now = int(time.time())
value = display_name if display_name else None

conn = sqlite3.connect(db_path)
conn.execute(
    """
    UPDATE sessions
    SET tmux_name = ?1,
        display_name = COALESCE(?2, display_name),
        updated_at = ?3,
        last_seen_at = ?3
    WHERE tmux_name = ?4
    """,
    (new_tmux_name, value, now, old_tmux_name),
)
conn.commit()
PY
}

_tmux_registry_remove_session() {
  local tmux_name="$1"
  local db_path
  db_path="$(_tmux_session_registry_path)"

  [[ -f "$db_path" ]] || return 0
  have_cmd python3 || return 1

  python3 - "$db_path" "$tmux_name" <<'PY'
import sqlite3
import sys

db_path, tmux_name = sys.argv[1:3]
conn = sqlite3.connect(db_path)
conn.execute("DELETE FROM sessions WHERE tmux_name = ?1", (tmux_name,))
conn.commit()
PY
}

_tmux_registry_upsert_current_session() {
  local session_name="$1"
  local session_dir repo_root branch
  session_dir="$(tmux display-message -t "$session_name" -p '#{pane_current_path}' 2>/dev/null || echo "")"
  repo_root="$(_tmux_shared_root_for_path "$session_dir" 2>/dev/null || true)"
  branch="$(_tmux_branch_for_path "$session_dir" 2>/dev/null || true)"
  [[ -n "$repo_root" && -n "$branch" ]] || return 1
  _tmux_registry_upsert_session "$repo_root" "$branch" "$session_dir" "$session_name"
}

_tmux_registry_sync_active_workspace_sessions() {
  local repo_root="$1"
  [[ -n "$repo_root" ]] || return 1
  tmux_available || return 1

  local session_name session_dir session_root branch display_name
  while IFS= read -r session_name; do
    [[ -n "$session_name" ]] || continue
    display_name="$(tmux show-option -t "$session_name" -qv @orchestra_display_name 2>/dev/null || true)"
    [[ -n "$display_name" ]] || continue
    session_dir="$(tmux display-message -t "$session_name" -p '#{pane_current_path}' 2>/dev/null || echo "")"
    session_root="$(_tmux_shared_root_for_path "$session_dir" 2>/dev/null || true)"
    [[ "$session_root" == "$repo_root" ]] || continue
    branch="$(_tmux_branch_for_path "$session_dir" 2>/dev/null || true)"
    [[ -n "$branch" ]] || continue
    _tmux_registry_upsert_session "$repo_root" "$branch" "$session_dir" "$session_name" >/dev/null 2>&1 || true
  done < <(tmux list-sessions -F '#{session_name}' 2>/dev/null || true)
}

_tmux_status_escape_text() {
  local text="$1"
  text="${text//\#/##}"
  printf '%s' "$text"
}

_tmux_truncate_tab_label() {
  local label="$1"
  local max_len="${2:-18}"
  if (( ${#label} > max_len )); then
    printf '%s...' "${label:0:$((max_len - 3))}"
  else
    printf '%s' "$label"
  fi
}

_tmux_workspace_session_rows() {
  local current_session="$1"
  local repo_root="$2"
  local db_path active_file
  db_path="$(_tmux_session_registry_path)"

  [[ -f "$db_path" ]] || return 0
  have_cmd python3 || return 0

  active_file="$(mktemp)"
  tmux list-sessions -F '#{session_name}' > "$active_file" 2>/dev/null || {
    rm -f "$active_file"
    return 0
  }

  python3 - "$db_path" "$repo_root" "$current_session" "$active_file" <<'PY'
import sqlite3
import sys

db_path, repo_root, current_session, active_path = sys.argv[1:5]
with open(active_path, "r", encoding="utf-8") as handle:
    active = {line.strip() for line in handle if line.strip()}

conn = sqlite3.connect(db_path)
rows = conn.execute(
    """
    SELECT tmux_name, COALESCE(display_name, '')
    FROM sessions
    WHERE repo_root = ?1
    ORDER BY worktree_path COLLATE NOCASE, created_at, tmux_name COLLATE NOCASE
    """,
    (repo_root,),
).fetchall()

filtered = []
seen = set()
for name, display_name in rows:
    if name in active and name not in seen:
        filtered.append((name, display_name or ""))
        seen.add(name)

if not filtered:
    raise SystemExit(0)

max_tabs = 8
if len(filtered) <= max_tabs:
    visible = filtered
    prefix = suffix = False
else:
    try:
        current_index = [name for name, _ in filtered].index(current_session)
    except ValueError:
        current_index = 0
    start = max(0, min(current_index - 2, len(filtered) - max_tabs))
    end = min(len(filtered), start + max_tabs)
    visible = filtered[start:end]
    prefix = start > 0
    suffix = end < len(filtered)

if prefix:
    print("__ellipsis__\t")
for name, display_name in visible:
    print(f"{name}\t{display_name}")
if suffix:
    print("__ellipsis__\t")
PY
  local status=$?
  rm -f "$active_file"
  return $status
}

_tmux_workspace_session_tabs() {
  local current_session="$1"
  local fallback_display_name="$2"
  local session_dir repo_root rows tabs name display_name label escaped_label divider

  session_dir="$(tmux display-message -t "$current_session" -p '#{pane_current_path}' 2>/dev/null || echo "")"
  repo_root="$(_tmux_shared_root_for_path "$session_dir" 2>/dev/null || true)"
  if [[ -n "$repo_root" ]]; then
    _tmux_registry_sync_active_workspace_sessions "$repo_root" >/dev/null 2>&1 || true
    rows="$(_tmux_workspace_session_rows "$current_session" "$repo_root" 2>/dev/null || true)"
  else
    rows=""
  fi

  if [[ -z "$rows" ]]; then
    label="$(_tmux_truncate_tab_label "$fallback_display_name")"
    escaped_label="$(_tmux_status_escape_text "$label")"
    printf '#[fg=white,bg=colour22,bold] %s #[default]' "$escaped_label"
    return
  fi

  tabs=""
  divider="#[fg=white,bg=colour22] | #[default]"
  while IFS=$'\t' read -r name display_name; do
    [[ -n "$name" ]] || continue
    if [[ -n "$tabs" ]]; then
      tabs+="$divider"
    fi
    if [[ "$name" == "__ellipsis__" ]]; then
      tabs+="#[fg=white,bg=colour22]  ...  #[default]"
      continue
    fi

    if [[ -z "$display_name" ]]; then
      display_name="$(tmux_format_session_display "$name" without-timestamp)"
    fi
    label="$(_tmux_truncate_tab_label "$display_name")"
    escaped_label="$(_tmux_status_escape_text "$label")"

    if [[ "$name" == "$current_session" ]]; then
      tabs+="#[fg=white,bg=colour22,bold]  [${escaped_label}]  #[default]"
    else
      tabs+="#[fg=white,bg=colour22]  ${escaped_label}  #[default]"
    fi
  done <<< "$rows"

  printf '%s' "$tabs"
}

_tmux_orchestra_status_left() {
  local session_name="$1"
  local session_display_name="$3"
  _tmux_workspace_session_tabs "$session_name" "$session_display_name"
}

_tmux_orchestra_status_right() {
  printf ''
}

_tmux_configure_orchestra_bindings() {
  local bridge
  bridge="$(_orchestra_bridge_script)"
  [[ -f "$bridge" ]] || return

  local quoted_bridge rename_command prompt_command next_command previous_command help_command
  printf -v quoted_bridge '%q' "$bridge"
  rename_command="$quoted_bridge manual-rename-session \\\"#{session_name}\\\" \\\"%%\\\" >/dev/null 2>&1"
  prompt_command="command-prompt -p 'Rename Orchestra session:' 'run-shell -b \"$rename_command\"'"
  next_command="$quoted_bridge cycle-workspace-session \\\"#{session_name}\\\" next \\\"#{client_tty}\\\" >/dev/null 2>&1"
  previous_command="$quoted_bridge cycle-workspace-session \\\"#{session_name}\\\" previous \\\"#{client_tty}\\\" >/dev/null 2>&1"
  help_command="$quoted_bridge tmux-help-popup \\\"#{client_tty}\\\" >/dev/null 2>&1"

  tmux bind-key -T prefix '?' if-shell -F '#{@orchestra_display_name}' \
    "run-shell -b \"$help_command\"" 'list-keys -N' >/dev/null 2>&1 || true
  tmux bind-key -T prefix r if-shell -F '#{@orchestra_display_name}' \
    "$prompt_command" 'refresh-client -S' >/dev/null 2>&1 || true
  tmux bind-key -T prefix '>' if-shell -F '#{@orchestra_display_name}' \
    "run-shell -b \"$next_command\"" 'switch-client -n' >/dev/null 2>&1 || true
  tmux bind-key -T prefix '<' if-shell -F '#{@orchestra_display_name}' \
    "run-shell -b \"$previous_command\"" 'switch-client -p' >/dev/null 2>&1 || true
  tmux bind-key -r -T prefix Right if-shell -F '#{@orchestra_display_name}' \
    "run-shell -b \"$next_command\"" 'select-pane -R' >/dev/null 2>&1 || true
  tmux bind-key -r -T prefix Left if-shell -F '#{@orchestra_display_name}' \
    "run-shell -b \"$previous_command\"" 'select-pane -L' >/dev/null 2>&1 || true
}

_tmux_configure_orchestra_status() {
  local session_name="$1"
  local worktree_name="$2"
  local session_display_name="${3:-}"
  local status_left status_right
  if [[ -z "$session_display_name" ]]; then
    session_display_name="$(tmux_format_session_display "$session_name" without-timestamp)"
  fi
  status_left="$(_tmux_orchestra_status_left "$session_name" "$worktree_name" "$session_display_name")"
  status_right="$(_tmux_orchestra_status_right "$worktree_name")"

  tmux set-option -t "$session_name" @orchestra_display_name "$session_display_name" >/dev/null 2>&1 || true
  tmux set-option -t "$session_name" @orchestra_worktree_name "$worktree_name" >/dev/null 2>&1 || true
  tmux set-option -t "$session_name" status on >/dev/null 2>&1 || true
  tmux set-option -t "$session_name" status-position bottom >/dev/null 2>&1 || true
  tmux set-option -t "$session_name" status-left "$status_left" >/dev/null 2>&1 || true
  tmux set-option -t "$session_name" status-left-length 1000 >/dev/null 2>&1 || true
  tmux set-option -t "$session_name" status-right "$status_right" >/dev/null 2>&1 || true
  tmux set-option -t "$session_name" status-right-length 0 >/dev/null 2>&1 || true
  tmux set-option -t "$session_name" window-status-format "" >/dev/null 2>&1 || true
  tmux set-option -t "$session_name" window-status-current-format "" >/dev/null 2>&1 || true
  tmux set-option -t "$session_name" window-status-separator "" >/dev/null 2>&1 || true
  _tmux_configure_orchestra_bindings
}

_tmux_refresh_orchestra_session_status() {
  local session_name="$1"
  local session_dir branch_name worktree_name display_name old_pwd

  session_dir="$(tmux display-message -t "$session_name" -p '#{pane_current_path}' 2>/dev/null || echo "")"
  branch_name=""
  if [[ -n "$session_dir" && -d "$session_dir" ]]; then
    old_pwd="$PWD"
    cd "$session_dir" 2>/dev/null || true
    branch_name="$(git_current_branch 2>/dev/null || git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
    cd "$old_pwd" 2>/dev/null || true
  fi

  if [[ -n "$branch_name" && "$branch_name" != "detached" ]]; then
    worktree_name="$branch_name"
  elif [[ -n "$session_dir" && -d "$session_dir" ]]; then
    worktree_name="$(basename "$session_dir")"
  else
    worktree_name="$branch_name"
  fi

  display_name="$(tmux show-option -t "$session_name" -qv @orchestra_display_name 2>/dev/null || true)"
  _tmux_configure_orchestra_status "$session_name" "$worktree_name" "$display_name"
}

# Helper: split a string by multi-char delimiter into bash array named by ref
# Usage: _tmux_split_by_delim "string" "::" out_array_name
_tmux_split_by_delim() {
  local _s="$1" _d="$2" _ref="$3"
  local _arr=()
  if [[ -z "$_d" ]]; then
    _arr=("$_s")
  else
    while :; do
      if [[ "$_s" == *"$_d"* ]]; then
        _arr+=("${_s%%"$_d"*}")
        _s="${_s#*"$_d"}"
      else
        _arr+=("$_s")
        break
      fi
    done
  fi
  # Use printf with %q to properly quote array elements for eval
  local _quoted=()
  local _item
  for _item in "${_arr[@]}"; do
    printf -v _q "%q" "$_item"
    _quoted+=("$_q")
  done
  eval "$_ref=( ${_quoted[*]} )"
}


# Check if tmux is available
tmux_available() {
  have_cmd tmux
}

# Check if currently inside a tmux session
tmux_inside_session() {
  [[ -n "${TMUX-}" ]]
}

# Generate random readable name for tmux sessions
tmux_generate_readable_name() {
  local adjectives=(
    "swift" "brave" "clever" "gentle" "bright" "calm" "eager" "fierce" "happy" "kind"
    "lively" "noble" "proud" "quick" "smart" "wise" "bold" "cool" "daring" "epic"
    "fuzzy" "jolly" "lucky" "merry" "peppy" "rosy" "sunny" "zesty" "crisp" "fresh"
  )
  local animals=(
    "bear" "wolf" "fox" "eagle" "hawk" "lion" "tiger" "panda" "otter" "seal"
    "whale" "shark" "dolphin" "falcon" "raven" "deer" "moose" "lynx" "badger" "heron"
    "phoenix" "dragon" "griffin" "unicorn" "pegasus" "kraken" "sphinx" "chimera" "hydra" "basilisk"
  )
  
  local adj_idx=$((RANDOM % ${#adjectives[@]}))
  local animal_idx=$((RANDOM % ${#animals[@]}))
  
  echo "${adjectives[$adj_idx]}_${animals[$animal_idx]}"
}

# --------------------------- Session Management -----------------------------

# Create a new tmux session
# Usage: tmux_create_session <session_name> <working_directory>
tmux_create_session() {
  local session_name="$1"
  local working_dir="$2"
  
  if ! tmux_available; then
    err "tmux not installed"
    return 1
  fi
  
  # Get repository info from the working directory context
  local repo_name=""
  local branch_name=""
  local repo_root=""
  local old_pwd="$PWD"
  local resolved_working_dir="$working_dir"
  if [[ -n "$working_dir" && -d "$working_dir" ]]; then
    resolved_working_dir="$(cd "$working_dir" 2>/dev/null && pwd -P || printf '%s' "$working_dir")"
  fi
  
  # Change to working directory to get accurate git info
  cd "$resolved_working_dir" 2>/dev/null || true
  
  if command -v git >/dev/null 2>&1 && git rev-parse --git-dir >/dev/null 2>&1; then
    repo_name="$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")"
    branch_name="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "detached")"
    repo_root="$(git_shared_root 2>/dev/null || git_repo_root 2>/dev/null || true)"
  fi
  
  # Restore original directory
  cd "$old_pwd" 2>/dev/null || true
  
  # Ensure orchestra prefix (configurable delimiter) on session name to mark origin
  local ORCH_PREFIX
  ORCH_PREFIX="$(_tmux_orch_prefix)"
  if [[ "$session_name" != ${ORCH_PREFIX}* ]]; then
    session_name="${ORCH_PREFIX}${session_name}"
  fi
  
  # Create session with custom Orchestra status configuration
  tmux new-session -Ad -s "$session_name" -c "$resolved_working_dir" >/dev/null 2>&1 || {
    err "Failed to create tmux session: $session_name"
    return 1
  }
  
  local worktree_name=""
  if [[ -n "$branch_name" && "$branch_name" != "detached" ]]; then
    worktree_name="$branch_name"
  else
    worktree_name="$(basename "$resolved_working_dir")"
  fi

  if [[ -n "$repo_root" && -n "$branch_name" && "$branch_name" != "detached" ]]; then
    _tmux_registry_upsert_session "$repo_root" "$branch_name" "$resolved_working_dir" "$session_name" >/dev/null 2>&1 || true
  fi

  # Customize the default status bar to include Orchestra info on the left
  if [[ -n "$repo_name" ]]; then
    _tmux_configure_orchestra_status "$session_name" "$worktree_name"
  fi
  
  echo "$session_name"
}

# Generate a short hash from a path for unique session identification
# Usage: tmux_path_hash <path>
tmux_path_hash() {
  local path="$1"
  # Use MD5 hash of the path, take first 8 chars
  # Works on both macOS and Linux
  if command -v md5sum >/dev/null 2>&1; then
    echo -n "$path" | md5sum | cut -c1-8
  elif command -v md5 >/dev/null 2>&1; then
    echo -n "$path" | md5 -q | cut -c1-8
  else
    # Fallback: use cksum if neither md5 available
    echo -n "$path" | cksum | cut -d' ' -f1 | cut -c1-8
  fi
}

# Alias for backward compatibility
tmux_repo_hash() {
  tmux_path_hash "$1"
}

# Ensure a session exists for slug+name in worktree; prints session name
# Creates sessions with format: [worktreename]_[worktreetreehash]_[datetime]_[readable_name]
# Usage: tmux_ensure_session <slug> <name> <worktree_path>
tmux_ensure_session() {
  local slug="$1"
  local name="$2" 
  local wt="$3"
  local d
  d="$(_tmux_delim)"
  local date_part time_part
  date_part="$(date +%Y%m%d)"
  time_part="$(date +%H%M%S)"
  
  # Use a repo-scoped hash to avoid cross-repo collisions
  # Hash the absolute worktree path (backward-compat listing supports old slug-hash)
  local worktree_hash
  worktree_hash="$(tmux_path_hash "$wt")"
  
  # If name is provided, use it; otherwise generate a random readable name with auto_ prefix
  if [[ -z "$name" || "$name" == "main" ]]; then
    name="auto_$(tmux_generate_readable_name)"
  fi
  
  local sess="${slug}${d}${worktree_hash}${d}${date_part}${d}${time_part}${d}${name}"
  tmux_create_session "$sess" "$wt"
  _tmux_source_command_hook "$sess"
}

# Check if a session exists
# Usage: tmux_session_exists <session_name>
tmux_session_exists() {
  local session_name="$1"
  tmux_available && tmux has-session -t "$session_name" 2>/dev/null
}

# Kill/delete a tmux session
# Usage: tmux_kill_session <session_name>
tmux_kill_session() {
  local session_name="$1"
  
  if ! tmux_available; then
    err "tmux not installed"
    return 1
  fi
  
  tmux kill-session -t "$session_name" 2>/dev/null || {
    err "Failed to kill session: $session_name"
    return 1
  }
  _tmux_registry_remove_session "$session_name" >/dev/null 2>&1 || true
}

# Attach or switch to session
# Usage: tmux_attach_session <session_name>
tmux_attach_session() {
  local sess="$1"
  
  if ! tmux_available; then
    err "tmux not installed"
    return 1
  fi

  _tmux_source_command_hook "$sess"
  
  # Get repository info for terminal title and banner update
  local repo_name=""
  local branch_name=""
  local session_dir=""
  
  # Try to get the session's working directory
  session_dir="$(tmux display-message -t "$sess" -p '#{pane_current_path}' 2>/dev/null || echo "")"
  
  if [[ -n "$session_dir" ]] && [[ -d "$session_dir" ]]; then
    # Get git info from session's directory
    local old_pwd="$PWD"
    cd "$session_dir" 2>/dev/null || true
    
    if command -v git >/dev/null 2>&1 && git rev-parse --git-dir >/dev/null 2>&1; then
      repo_name="$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")"
      branch_name="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "detached")"
    fi
    
    cd "$old_pwd" 2>/dev/null || true
  else
    # Fallback to current directory
    if command -v git >/dev/null 2>&1 && git rev-parse --git-dir >/dev/null 2>&1; then
      repo_name="$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")"
      branch_name="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "detached")"
    fi
  fi
  
  # Set terminal window title (works in most terminal emulators)
  if [[ -n "$repo_name" ]]; then
    printf "\033]0;🎼 Orchestra: %s [%s]\007" "$repo_name" "$branch_name" >/dev/tty 2>/dev/null || true
  fi
  
  # Show welcome message when attaching
  if [[ -n "$repo_name" ]]; then
    tmux display-message -t "$sess" -d 2000 \
      "🎼 Orchestra Session | Repository: $repo_name | Branch: $branch_name" 2>/dev/null || true
  fi

  _tmux_refresh_orchestra_session_status "$sess"
  
  if tmux_inside_session; then
    tmux switch-client -t "$sess" >/dev/null 2>&1 || true
  else
    tmux attach -t "$sess" >/dev/null 2>&1 || true
  fi
}

# Rename a tmux session while preserving the worktree prefix, repo hash, and datetime
# Usage: tmux_rename_session <old_session_name> <new_name>
tmux_rename_session() {
  local old_session="$1"
  local new_name="$2"
  
  if ! tmux_available; then
    err "tmux not installed"
    return 1
  fi
  
  local d ORCH_PREFIX
  d="$(_tmux_delim)"
  ORCH_PREFIX="$(_tmux_orch_prefix)"
  
  # Detect and strip orchestra prefix for parsing
  local orch=""
  local base="$old_session"
  if [[ "$base" == ${ORCH_PREFIX}* ]]; then
    orch="$ORCH_PREFIX"
    base="${base#${ORCH_PREFIX}}"
  fi
  
  # Split by current delimiter and scan from the right to tolerate delimiter inside segments
  local parts
  _tmux_split_by_delim "$base" "$d" parts
  
  local n=${#parts[@]}
  local idx_time=-1 idx_date=-1 idx_hash=-1
  local i
  for (( i=n-1; i>=0; i-- )); do
    if [[ ${parts[$i]} =~ ^[0-9]{6}$ ]]; then idx_time=$i; break; fi
  done
  if (( idx_time > 0 )) && [[ ${parts[$((idx_time-1))]} =~ ^[0-9]{8}$ ]]; then
    idx_date=$((idx_time-1))
  fi
  if (( idx_date > 0 )) && [[ ${parts[$((idx_date-1))]} =~ ^[0-9a-f]{8}$ ]]; then
    idx_hash=$((idx_date-1))
  fi

  local prefix=""
  if (( idx_time >= 0 && idx_date >= 0 )); then
    # Build prefix: slug + d + [hash + d] + date + d + time + d
    local upto=$idx_time
    local j
    for (( j=0; j<=upto; j++ )); do
      if (( j > 0 )); then prefix+="$d"; fi
      prefix+="${parts[$j]}"
    done
    prefix+="$d"

    # Debug logging for rename parsing
    if [[ -n "${GW_DEBUG_RENAME-}" || -n "${DEBUG-}" ]]; then
      >&2 echo "[orchestra] rename DEBUG: old='$old_session' base='$base' delim='$d'"
      >&2 echo "[orchestra] parts: ${parts[*]}"
      >&2 echo "[orchestra] idx_time=$idx_time idx_date=$idx_date idx_hash=$idx_hash"
      >&2 echo "[orchestra] prefix='${orch}${prefix}' new_name='$new_name'"
    fi
  else
    err "Invalid session format"
    return 1
  fi
  
  local new_session="${orch}${prefix}${new_name}"
  
  tmux rename-session -t "$old_session" "$new_session" 2>/dev/null || {
    err "Failed to rename session"
    return 1
  }
  _tmux_registry_rename_session "$old_session" "$new_session" "$(format_session_display_name "$new_name")" >/dev/null 2>&1 || true
  local worktree_name
  worktree_name="$(tmux show-option -t "$new_session" -qv @orchestra_worktree_name 2>/dev/null || true)"
  if [[ -z "$worktree_name" ]]; then
    local session_dir old_pwd branch_name
    session_dir="$(tmux display-message -t "$new_session" -p '#{pane_current_path}' 2>/dev/null || echo "")"
    if [[ -n "$session_dir" && -d "$session_dir" ]]; then
      old_pwd="$PWD"
      cd "$session_dir" 2>/dev/null || true
      branch_name="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")"
      cd "$old_pwd" 2>/dev/null || true
      if [[ -n "$branch_name" && "$branch_name" != "detached" ]]; then
        worktree_name="$branch_name"
      else
        worktree_name="$(basename "$session_dir")"
      fi
    fi
  fi
  _tmux_configure_orchestra_status "$new_session" "$worktree_name"
  >&2 echo "✏️  Renamed session to: $new_name"
  return 0
}

# --------------------------- Session Discovery ------------------------------

# Get tmux sessions for a slug prefix; prints session names sorted by last attached/activity
# Expected session format: [worktreename]_[worktreehash]_[datetime]_[readable_name]
# Usage: tmux_list_sessions_for_slug <slug> [worktree_path]
tmux_list_sessions_for_slug() {
  local slug="$1"
  local worktree_path="${2:-}"
  tmux_available || return 0
  
  local d ORCH_PREFIX
  d="$(_tmux_delim)"
  ORCH_PREFIX="$(_tmux_orch_prefix)"
  
  # If worktree_path provided, match hash-based and no-hash prefixes
  if [[ -n "$worktree_path" ]]; then
    local hash_slug hash_path branch_name
    hash_slug="$(tmux_path_hash "$slug")"
    hash_path="$(tmux_path_hash "$worktree_path")"
    branch_name="$(git_worktree_path_to_branch "$worktree_path" 2>/dev/null || true)"

    local p1_new="${ORCH_PREFIX}${slug}${d}${hash_slug}${d}"
    local p2_new="${slug}${d}${hash_slug}${d}"
    local p1_old="${ORCH_PREFIX}${slug}${d}${hash_path}${d}"
    local p2_old="${slug}${d}${hash_path}${d}"
    local p1_branch_hash=""
    local p2_branch_hash=""
    if [[ -n "$branch_name" ]]; then
      p1_branch_hash="${ORCH_PREFIX}${branch_name}${d}${hash_path}${d}"
      p2_branch_hash="${branch_name}${d}${hash_path}${d}"
    fi

    # List sessions with any known prefix variant
    tmux list-sessions -F '#{session_name}|||#{session_last_attached}|||#{session_activity}' 2>/dev/null \
      | sed 's/|||/\t/g' \
      | awk -v a="$p1_new" -v b="$p2_new" -v c="$p1_old" -v d="$p2_old" -v e="$p1_branch_hash" -v f="$p2_branch_hash" 'BEGIN{FS="\t"} (length(a)>0 && index($1, a)==1) || (length(b)>0 && index($1, b)==1) || (length(c)>0 && index($1, c)==1) || (length(d)>0 && index($1, d)==1) || (length(e)>0 && index($1, e)==1) || (length(f)>0 && index($1, f)==1) {print $1"\t"$2"\t"$3}' \
      | sort -t $'\t' -k2,2nr -k3,3nr \
      | awk -F '\t' '{print $1}' | awk '!seen[$0]++' || true
  else
    # New-format prefix matching without hash: orchestra__slug__... or slug__...
    local prefix1="${ORCH_PREFIX}${slug}${d}"
    local prefix2="${slug}${d}"
    tmux list-sessions -F '#{session_name}|||#{session_last_attached}|||#{session_activity}' 2>/dev/null \
      | sed 's/|||/\t/g' \
      | awk -v p1="$prefix1" -v p2="$prefix2" 'BEGIN{FS="\t"} index($1, p1)==1 || index($1, p2)==1 {print $1"\t"$2"\t"$3}' \
      | sort -t $'\t' -k2,2nr -k3,3nr \
      | awk -F '\t' '{print $1}' || true
  fi
}

# List all tmux sessions
# Usage: tmux_list_all_sessions
tmux_list_all_sessions() {
  tmux_available || return 0
  tmux list-sessions -F '#{session_name}' 2>/dev/null || true
}

# Find sessions matching a pattern
# Usage: tmux_find_session <pattern>
tmux_find_session() {
  local pattern="$1"
  
  if ! tmux_available; then
    return 1
  fi
  
  # Try exact match first
  if tmux_session_exists "$pattern"; then
    echo "$pattern"
    return 0
  fi
  
  # Try pattern match
  tmux_list_all_sessions | grep -E "$pattern" | head -1 || true
}

# --------------------------- Session Information ----------------------------

# Helper function to format session display names
# Input: session_content (e.g., "opencode_fixing_auth_bug" or "my_feature_work")
# Output: "Opencode Fixing Auth Bug" or "My Feature Work"
format_session_display_name() {
  local session_content="$1"
  local d
  d="$(_tmux_delim)"
  local ORCH_PREFIX
  ORCH_PREFIX="$(_tmux_orch_prefix)"
  if [[ "$session_content" == ${ORCH_PREFIX}* ]]; then
    session_content="${session_content#${ORCH_PREFIX}}"
  fi
  if [[ "$session_content" == auto_* ]]; then
    session_content="${session_content#auto_}"
  fi
  local description
  description="$(echo "$session_content" | tr '_-' ' ')"
  description="$(echo "$description" | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2));}1')"
  echo "$description"
}

_tmux_format_session_timestamp() {
  local date_part="$1"
  local time_part="$2"

  if [[ ! "$date_part" =~ ^[0-9]{8}$ || ! "$time_part" =~ ^[0-9]{6}$ ]]; then
    printf '%s %s\n' "$date_part" "$time_part"
    return
  fi

  local month day hour minute ampm display_hour
  month=$((10#${date_part:4:2}))
  day=$((10#${date_part:6:2}))
  hour=$((10#${time_part:0:2}))
  minute="${time_part:2:2}"

  ampm="am"
  display_hour="$hour"
  if (( hour >= 12 )); then
    ampm="pm"
    if (( hour > 12 )); then
      display_hour=$((hour - 12))
    fi
  fi
  if (( hour == 0 )); then
    display_hour=12
  fi

  local month_names=("" "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
  local month_name="???"
  if (( month > 0 && month <= 12 )); then
    month_name="${month_names[$month]}"
  fi

  printf '%s %s %s:%s%s\n' "$month_name" "$day" "$display_hour" "$minute" "$ampm"
}

# Parse session name and format for display
# Input: session_name (format: worktreename_repohash_YYYYMMDD_HHMMSS_readable_name or old formats)
# Output: formatted display name
# Usage: tmux_format_session_display <session_name>
tmux_format_session_display() {
  local session_name="$1"
  local timestamp_mode="${2:-with-timestamp}"

  # Handle temporary renaming sessions first
  if [[ "$session_name" =~ _renaming$ ]]; then
    # Extract base name before _renaming suffix
    local base_name="${session_name%_renaming}"
    # Recursively process the base name to get proper display
    tmux_format_session_display "$base_name" "$timestamp_mode"
    return $?
  fi
  
  local d ORCH_PREFIX
  d="$(_tmux_delim)"
  ORCH_PREFIX="$(_tmux_orch_prefix)"

  if [[ "$session_name" == ${ORCH_PREFIX}* ]]; then
    session_name="${session_name#${ORCH_PREFIX}}"
  elif [[ "$session_name" =~ ^orchestra_(.+)$ ]]; then
    session_name="${BASH_REMATCH[1]}"
  fi

  local parts
  _tmux_split_by_delim "$session_name" "$d" parts
  local idx_time=-1 idx_date=-1 i
  for (( i=${#parts[@]}-1; i>=0; i-- )); do
    if [[ ${parts[$i]} =~ ^[0-9]{6}$ ]] && (( i > 0 )) && [[ ${parts[$((i-1))]} =~ ^[0-9]{8}$ ]]; then
      idx_time=$i
      idx_date=$((i-1))
      break
    fi
  done
  if (( idx_time >= 0 && idx_date >= 0 )); then
    local readable_name=""
    local j
    for (( j=idx_time+1; j<${#parts[@]}; j++ )); do
      if [[ -n "$readable_name" ]]; then
        readable_name+="$d"
      fi
      readable_name+="${parts[$j]}"
    done
    if [[ -n "$readable_name" ]]; then
      local formatted_name timestamp
      formatted_name="$(format_session_display_name "$readable_name")"
      timestamp="$(_tmux_format_session_timestamp "${parts[$idx_date]}" "${parts[$idx_time]}")"
      if [[ "$timestamp_mode" == "without-timestamp" ]]; then
        echo "$formatted_name"
      else
        echo "${formatted_name} (${timestamp})"
      fi
      return 0
    fi
  fi
  
  # New format with repo hash: worktreename_repohash_YYYYMMDD_HHMMSS_readable_name
  if [[ "$session_name" =~ ^[^_]+_[a-f0-9]{8}_([0-9]{8})_([0-9]{6})_(.+)$ ]]; then
    local date_part="${BASH_REMATCH[1]}"  # YYYYMMDD
    local time_part="${BASH_REMATCH[2]}"  # HHMMSS
    local readable_name="${BASH_REMATCH[3]}"  # readable_name
    
    # Parse date: YYYYMMDD -> Jul 21
    local year="${date_part:0:4}"
    local month="${date_part:4:2}"
    local day="${date_part:6:2}"
    
    # Parse time: HHMMSS -> 12:30am
    local hour="${time_part:0:2}"
    local minute="${time_part:2:2}"
    
    # Convert to 12-hour format
    local ampm="am"
    local display_hour="$hour"
    if [[ "$hour" -ge 12 ]]; then
      ampm="pm"
      if [[ "$hour" -gt 12 ]]; then
        display_hour=$((hour - 12))
      fi
    fi
    if [[ "$hour" == "00" ]]; then
      display_hour="12"
    fi
    
    # Remove leading zero from hour
    display_hour="${display_hour#0}"
    
    # Convert month number to name
    local month_names=("" "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
    local month_name="${month_names[${month#0}]}"
    
    # Remove leading zero from day
    local display_day="${day#0}"
    
    # Format the readable name with proper formatting
    local formatted_name="$(format_session_display_name "$readable_name")"

    if [[ "$timestamp_mode" == "without-timestamp" ]]; then
      echo "$formatted_name"
    else
      echo "${formatted_name} (${month_name} ${display_day} ${display_hour}:${minute}${ampm})"
    fi
  # Fallback: Try format without repo hash: appname_description_YYYYMMDD_HHMMSS
  elif [[ "$session_name" =~ ^(.*)_[0-9]{8}_[0-9]{6}$ ]]; then
    local session_content="${BASH_REMATCH[1]}"  # appname_description
    
    # Convert to proper display format
    local formatted_name="$(format_session_display_name "$session_content")"
    echo "$formatted_name"
  else
    # Check for old format without repo hash: worktreename_YYYYMMDD_HHMMSS_readable_name
    if [[ "$session_name" =~ ^[^_]+_([0-9]{8})_([0-9]{6})_(.+)$ ]]; then
      local date_part="${BASH_REMATCH[1]}"  # YYYYMMDD
      local time_part="${BASH_REMATCH[2]}"  # HHMMSS
      local readable_name="${BASH_REMATCH[3]}"  # readable_name
      
      # Parse date: YYYYMMDD -> Jul 21
      local year="${date_part:0:4}"
      local month="${date_part:4:2}"
      local day="${date_part:6:2}"
      
      # Parse time: HHMMSS -> 12:30am
      local hour="${time_part:0:2}"
      local minute="${time_part:2:2}"
      
      # Convert to 12-hour format
      local ampm="am"
      local display_hour="$hour"
      if [[ "$hour" -ge 12 ]]; then
        ampm="pm"
        if [[ "$hour" -gt 12 ]]; then
          display_hour=$((hour - 12))
        fi
      fi
      if [[ "$hour" == "00" ]]; then
        display_hour="12"
      fi
      
      # Remove leading zero from hour
      display_hour="${display_hour#0}"
      
      # Convert month number to name
      local month_names=("" "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
      local month_name="${month_names[${month#0}]}"
      
      # Remove leading zero from day
      local display_day="${day#0}"
      
      # Format the readable name with proper formatting
      local formatted_name="$(format_session_display_name "$readable_name")"

      if [[ "$timestamp_mode" == "without-timestamp" ]]; then
        echo "$formatted_name"
      else
        echo "${formatted_name} (${month_name} ${display_day} ${display_hour}:${minute}${ampm})"
      fi
    else
      # Fallback for sessions that don't match either format
      echo "$session_name"
    fi
  fi
}

# Get active pane id for a session (best effort)
# Usage: tmux_get_active_pane <session_name>
tmux_get_active_pane() {
  local s="$1"
  tmux_available || return 1
  
  # Find active window id
  local win
  win="$(tmux list-windows -t "$s" -F '#{window_active} #{window_id}' 2>/dev/null | awk '$1==1{print $2; exit}')" || true
  [[ -z "$win" ]] && return 1
  
  # Find active pane id within that window
  tmux list-panes -t "$win" -F '#{pane_active} #{pane_id}' 2>/dev/null | awk '$1==1{print $2; exit}' || true
}

# Capture enhanced session preview showing current terminal view (bottom lines)
# Usage: tmux_session_preview <session_name>
tmux_session_preview() {
  local s="$1"
  tmux_available || { echo "(tmux not installed)"; return 0; }
  
  local pane
  pane="$(tmux_get_active_pane "$s" || true)"
  if [[ -z "$pane" ]]; then
    echo "(no active pane found)"
    return 0
  fi
  
  # Always capture from the BOTTOM of the terminal buffer (last visible lines)
  # -e flag preserves escape sequences for colors and formatting
  # -p prints to stdout
  # NO -S/-E flags means capture the current viewport (what's visible NOW)
  # This ensures we always see the bottom/most recent output
  local txt
  txt="$(tmux capture-pane -e -p -t "$pane" 2>/dev/null)"
  
  if [[ -z "$txt" ]]; then
    echo "(no output yet)"
    return 0
  fi
  
  # Check if the pane is idle (cursor at prompt, no running process)
  local pane_cmd pane_mode
  pane_cmd="$(tmux display-message -t "$pane" -p '#{pane_current_command}' 2>/dev/null || echo "")"
  pane_mode="$(tmux display-message -t "$pane" -p '#{pane_mode}' 2>/dev/null || echo "")"
  
  # Detect idle state: shell running and not in copy mode
  local is_idle="false"
  case "$pane_cmd" in
    bash|zsh|sh|fish|dash|ksh)
      # Shell is running, check if we're at a prompt (not in copy mode)
      if [[ "$pane_mode" == "" ]]; then
        is_idle="true"
      fi
      ;;
  esac
  
  # Optimized: Get terminal info in a single command instead of multiple calls
  local term_info has_rgb
  term_info="$(tmux show-environment -t "$s" TERM 2>/dev/null | cut -d= -f2 || echo "unknown")"
  
  # Fast check for RGB support without full grep
  if tmux show-options -t "$s" -s terminal-overrides 2>/dev/null | grep -q "RGB"; then
    has_rgb="true"
  else
    has_rgb="false"
  fi
  
  # For ANSI-preserved preview with color mode info
  # Add markers for the Rust parser to detect color capabilities and idle state
  if [[ "$has_rgb" == "true" || "$term_info" == *"direct"* || "$term_info" == *"truecolor"* ]]; then
    echo "<<<COLORMODE:RGB>>>"
  elif [[ "$term_info" == *"256color"* ]]; then
    echo "<<<COLORMODE:256>>>"
  else
    echo "<<<COLORMODE:16>>>"
  fi
  
  # Add idle marker so Rust can stop polling when terminal is idle
  if [[ "$is_idle" == "true" ]]; then
    echo "<<<IDLE:true>>>"
  else
    echo "<<<IDLE:false>>>"
  fi

  # If no content after processing, show placeholder
  if [[ -z "$txt" ]]; then
    echo "(session active, no visible output)"
  else
    echo "$txt"
  fi
}

# --------------------------- Advanced Operations ----------------------------

# Send keys to a session and press Enter
# Usage: tmux_send_keys <session_name> <command...>
tmux_send_keys() {
  local session_name="$1"; shift || true
  local command_line="$*"
  if ! tmux_available; then
    err "tmux not installed"
    return 1
  fi
  if [[ -z "$session_name" || -z "$command_line" ]]; then
    err "tmux_send_keys: session and command required"
    return 1
  fi
  tmux send-keys -t "$session_name" -l -- "$command_line" 2>/dev/null || return 1
  tmux send-keys -t "$session_name" C-m 2>/dev/null || return 1
}

# Show Orchestra tmux shortcuts in a popup modal.
# Usage: tmux_show_orchestra_help_popup [client_tty]
tmux_show_orchestra_help_popup() {
  local target_client="${1:-}"
  if ! tmux_available; then
    err "tmux not installed"
    return 1
  fi

  local target_args=()
  if [[ -n "$target_client" ]]; then
    target_args=(-c "$target_client")
  fi

  local popup_command
  popup_command='printf "%s\n" \
"Orchestra tmux shortcuts" \
"" \
"Ctrl+b, Left      Previous Orchestra session in this workspace" \
"Ctrl+b, Right     Next Orchestra session in this workspace" \
"Ctrl+b, <         Previous Orchestra session in this workspace" \
"Ctrl+b, >         Next Orchestra session in this workspace" \
"Ctrl+b, r         Rename the current Orchestra session" \
"Ctrl+b, d         Detach and return to Orchestra" \
"Ctrl+b, [         Copy/scroll mode" \
"Ctrl+b, ?         Show this help" \
"" \
"Press Enter to close..."; read -r _'

  tmux display-popup "${target_args[@]}" -E -w 78 -h 15 -T "Orchestra shortcuts" "$popup_command" >/dev/null 2>&1
}

# Find the adjacent active Orchestra session registered for the current repo.
# Usage: tmux_workspace_cycle_target <current_session> <next|previous>
tmux_workspace_cycle_target() {
  local current_session="$1"
  local direction="$2"
  local session_dir repo_root db_path active_file target

  if ! tmux_available; then
    err "tmux not installed"
    return 1
  fi
  if [[ -z "$current_session" ]]; then
    err "tmux_workspace_cycle_target: current session required"
    return 1
  fi
  case "$direction" in
    next|previous|prev) ;;
    *)
      err "tmux_workspace_cycle_target: direction must be next or previous"
      return 1
      ;;
  esac

  _tmux_registry_upsert_current_session "$current_session" >/dev/null 2>&1 || true

  session_dir="$(tmux display-message -t "$current_session" -p '#{pane_current_path}' 2>/dev/null || echo "")"
  repo_root="$(_tmux_shared_root_for_path "$session_dir" 2>/dev/null || true)"
  if [[ -z "$repo_root" ]]; then
    err "Unable to determine Orchestra workspace for session"
    return 1
  fi
  _tmux_registry_sync_active_workspace_sessions "$repo_root" >/dev/null 2>&1 || true

  db_path="$(_tmux_session_registry_path)"
  if [[ ! -f "$db_path" ]]; then
    err "No Orchestra session registry found"
    return 1
  fi
  have_cmd python3 || {
    err "python3 is required to read the Orchestra session registry"
    return 1
  }

  active_file="$(mktemp)"
  tmux list-sessions -F '#{session_name}' > "$active_file" 2>/dev/null || {
    rm -f "$active_file"
    err "Unable to list tmux sessions"
    return 1
  }

  target="$(python3 - "$db_path" "$repo_root" "$current_session" "$direction" "$active_file" <<'PY'
import sqlite3
import sys

db_path, repo_root, current_session, direction, active_path = sys.argv[1:6]
with open(active_path, "r", encoding="utf-8") as handle:
    active = {line.strip() for line in handle if line.strip()}

conn = sqlite3.connect(db_path)
rows = conn.execute(
    """
    SELECT tmux_name
    FROM sessions
    WHERE repo_root = ?1
    ORDER BY worktree_path COLLATE NOCASE, created_at, tmux_name COLLATE NOCASE
    """,
    (repo_root,),
).fetchall()

names = []
seen = set()
for (name,) in rows:
    if name in active and name not in seen:
        names.append(name)
        seen.add(name)

if len(names) < 2 or current_session not in seen:
    raise SystemExit(0)

index = names.index(current_session)
if direction == "next":
    index = (index + 1) % len(names)
else:
    index = (index - 1) % len(names)
print(names[index])
PY
  )"
  local query_status=$?
  rm -f "$active_file"
  if [[ $query_status -ne 0 ]]; then
    err "Unable to query Orchestra session registry"
    return 1
  fi
  [[ -n "$target" ]] || return 1
  printf '%s\n' "$target"
}

# Switch the current tmux client to an adjacent registered Orchestra session.
# Usage: tmux_cycle_workspace_session <current_session> <next|previous> [client_tty]
tmux_cycle_workspace_session() {
  local current_session="$1"
  local direction="$2"
  local target_client="${3:-}"
  local target_session

  if ! target_session="$(tmux_workspace_cycle_target "$current_session" "$direction")"; then
    tmux display-message -d 2500 "No other Orchestra sessions in this workspace" 2>/dev/null || true
    return 1
  fi

  _tmux_refresh_orchestra_session_status "$target_session" >/dev/null 2>&1 || true

  if [[ -n "$target_client" ]]; then
    tmux switch-client -c "$target_client" -t "$target_session" >/dev/null 2>&1 || return 1
  else
    tmux switch-client -t "$target_session" >/dev/null 2>&1 || return 1
  fi
}

# Load .env file if it exists
# Usage: tmux_load_env_file [env_file_path]
tmux_load_env_file() {
  local env_file="${1:-$PWD/.env}"
  if [[ -f "$env_file" ]]; then
    # Source the .env file, but only export ANTHROPIC_API_KEY
    set -a  # Auto-export variables
    source "$env_file"
    set +a  # Turn off auto-export
  fi
}

# Load Anthropic API key from config file or fallback to .env files
tmux_load_anthropic_api_key() {
  # First try to load from ~/.orchestra/config.json
  local config_file="$HOME/.orchestra/config.json"
  if [[ -f "$config_file" ]] && command -v jq >/dev/null 2>&1; then
    local api_key
    api_key="$(jq -r '.anthropic_api_key // empty' "$config_file" 2>/dev/null)"
    if [[ -n "$api_key" ]] && [[ "$api_key" != "null" ]]; then
      export ANTHROPIC_API_KEY="$api_key"
      return 0
    fi
  fi
  
  # Fallback to .env file loading (existing logic)
  tmux_load_env_file "$PWD/.env"
  
  # If still no API key, try repo root
  if [[ -z "${ANTHROPIC_API_KEY-}" ]]; then
    local root
    root="$(repo_root)"
    if [[ -n "$root" ]]; then
      tmux_load_env_file "$root/.env"
    fi
  fi
}

# Load OpenAI API key from config file or fallback to .env files
tmux_load_openai_api_key() {
  local config_file="$HOME/.orchestra/config.json"
  if [[ -f "$config_file" ]]; then
    local api_key=""
    if command -v jq >/dev/null 2>&1; then
      api_key="$(jq -r '.openai_api_key // empty' "$config_file" 2>/dev/null)"
    elif command -v python3 >/dev/null 2>&1; then
      api_key="$(python3 - "$config_file" <<'PY'
import json
import sys
try:
    data = json.load(open(sys.argv[1], 'r'))
except Exception:
    data = {}
value = data.get('openai_api_key') or ''
print(value)
PY
)"
    elif command -v node >/dev/null 2>&1; then
      api_key="$(node - "$config_file" <<'NODE'
const fs = require('fs');
let value = '';
try {
  const data = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
  value = data.openai_api_key || '';
} catch (err) {
  value = '';
}
process.stdout.write(value);
NODE
)"
    fi
    if [[ -n "$api_key" ]] && [[ "$api_key" != "null" ]]; then
      export OPENAI_API_KEY="$api_key"
      return 0
    fi
  fi

  tmux_load_env_file "$PWD/.env"
  if [[ -z "${OPENAI_API_KEY-}" ]]; then
    local root
    root="$(repo_root)"
    if [[ -n "$root" ]]; then
      tmux_load_env_file "$root/.env"
    fi
  fi
}

# Load primary AI provider from config (defaults to anthropic)
tmux_load_ai_primary_provider() {
  if [[ -n "${AI_PRIMARY_PROVIDER-}" ]]; then
    return 0
  fi
  local config_file="$HOME/.orchestra/config.json"
  local provider=""
  if [[ -f "$config_file" ]]; then
    if command -v jq >/dev/null 2>&1; then
      provider="$(jq -r '.ai_primary_provider // empty' "$config_file" 2>/dev/null)"
    elif command -v python3 >/dev/null 2>&1; then
      provider="$(python3 - "$config_file" <<'PY'
import json
import sys
try:
    data = json.load(open(sys.argv[1], 'r'))
except Exception:
    data = {}
value = data.get('ai_primary_provider') or ''
print(value)
PY
)"
    elif command -v node >/dev/null 2>&1; then
      provider="$(node - "$config_file" <<'NODE'
const fs = require('fs');
let value = '';
try {
  const data = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
  value = data.ai_primary_provider || '';
} catch (err) {
  value = '';
}
process.stdout.write(value);
NODE
)"
    fi
  fi
  provider="$(printf '%s' "$provider" | tr '[:upper:]' '[:lower:]')"
  if [[ "$provider" != "openai" && "$provider" != "anthropic" ]]; then
    provider="anthropic"
  fi
  export AI_PRIMARY_PROVIDER="$provider"
}

# Load OpenAI model from config (defaults to gpt-4o-mini)
tmux_load_openai_model() {
  if [[ -n "${OPENAI_MODEL-}" ]]; then
    return 0
  fi
  local config_file="$HOME/.orchestra/config.json"
  local model=""
  if [[ -f "$config_file" ]]; then
    if command -v jq >/dev/null 2>&1; then
      model="$(jq -r '.openai_model // empty' "$config_file" 2>/dev/null)"
    elif command -v python3 >/dev/null 2>&1; then
      model="$(python3 - "$config_file" <<'PY'
import json
import sys
try:
    data = json.load(open(sys.argv[1], 'r'))
except Exception:
    data = {}
value = data.get('openai_model') or ''
print(value)
PY
)"
    elif command -v node >/dev/null 2>&1; then
      model="$(node - "$config_file" <<'NODE'
const fs = require('fs');
let value = '';
try {
  const data = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
  value = data.openai_model || '';
} catch (err) {
  value = '';
}
process.stdout.write(value);
NODE
)"
    fi
  fi
  if [[ -z "$model" || "$model" == "null" ]]; then
    model="gpt-4o-mini"
  fi
  export OPENAI_MODEL="$model"
}

# Generate a descriptive name for a tmux session using AI
# Usage: tmux_generate_ai_session_name <session_name>
tmux_generate_ai_session_name() {
  local session="$1"
  
  # Load AI provider config and API keys
  tmux_load_ai_primary_provider
  tmux_load_openai_model
  tmux_load_anthropic_api_key
  tmux_load_openai_api_key

  local primary="${AI_PRIMARY_PROVIDER:-anthropic}"
  primary="$(printf '%s' "$primary" | tr '[:upper:]' '[:lower:]')"
  local provider=""
  if [[ "$primary" == "openai" ]]; then
    if [[ -n "${OPENAI_API_KEY-}" ]]; then
      provider="openai"
    elif [[ -n "${ANTHROPIC_API_KEY-}" ]]; then
      provider="anthropic"
    fi
  else
    if [[ -n "${ANTHROPIC_API_KEY-}" ]]; then
      provider="anthropic"
    elif [[ -n "${OPENAI_API_KEY-}" ]]; then
      provider="openai"
    fi
  fi

  if [[ -z "$provider" ]]; then
    err "No AI API key found in config file or .env file"
    return 1
  fi

  local openai_model="${OPENAI_MODEL:-gpt-4o-mini}"
  
  local pane_cmd pane_mode window_name pane_title alternate_on mouse_any_flag
  pane_cmd="$(tmux display-message -t "$session" -p '#{pane_current_command}' 2>/dev/null || echo "")"
  pane_mode="$(tmux display-message -t "$session" -p '#{pane_mode}' 2>/dev/null || echo "")"
  window_name="$(tmux display-message -t "$session" -p '#{window_name}' 2>/dev/null || echo "")"
  pane_title="$(tmux display-message -t "$session" -p '#{pane_title}' 2>/dev/null || echo "")"
  alternate_on="$(tmux display-message -t "$session" -p '#{alternate_on}' 2>/dev/null || echo "0")"
  mouse_any_flag="$(tmux display-message -t "$session" -p '#{mouse_any_flag}' 2>/dev/null || echo "0")"

  local history_temp=""
  local history_arg=""
  local history_dir="${ORCHESTRA_HISTORY_DIR:-$HOME/.orchestra/history}"
  local pane_id
  pane_id="$(tmux display-message -t "$session" -p '#{pane_id}' 2>/dev/null || echo "")"
  local pane_key=""
  if [[ -n "$pane_id" ]]; then
    pane_key="$(_orchestra_history_key "$pane_id")"
  fi
  local session_key
  session_key="$(_orchestra_history_key "$session")"

  local is_tui="false"
  if _tmux_is_tui_context "$pane_cmd" "$alternate_on" "$mouse_any_flag" "$pane_mode" "$window_name" "$pane_title"; then
    is_tui="true"
  fi

  local content=""
  local visible_content=""
  if [[ "$is_tui" == "true" ]]; then
    visible_content="$(tmux capture-pane -e -p -q -a -t "${pane_id:-$session}" 2>/dev/null || echo "")"
  fi
  local scrollback_content
  scrollback_content="$(tmux capture-pane -t "$session" -p -S -200 2>/dev/null)" || {
    err "Failed to capture tmux pane content"
    return 1
  }
  if [[ -n "$visible_content" ]]; then
    content="### Visible terminal view\n${visible_content}\n\n### Recent scrollback\n${scrollback_content}"
  else
    content="$scrollback_content"
  fi

  local history_path=""
  if [[ -n "$pane_key" && -f "$history_dir/$pane_key.log" ]]; then
    history_path="$history_dir/$pane_key.log"
  elif [[ -n "$session_key" && -f "$history_dir/$session_key.log" ]]; then
    history_path="$history_dir/$session_key.log"
  fi

  if [[ -n "$history_path" ]]; then
    if [[ "$OSTYPE" == "darwin"* ]]; then
      history_temp=$(mktemp -t gw_hist)
    else
      history_temp=$(mktemp)
    fi
    tail -n 50 "$history_path" > "$history_temp"
    history_arg="$history_temp"
  fi
  
  local metadata_block=""
  metadata_block+="Session metadata:\n"
  metadata_block+="- pane_current_command: ${pane_cmd:-unknown}\n"
  metadata_block+="- window_name: ${window_name:-}\n"
  metadata_block+="- pane_title: ${pane_title:-}\n"
  metadata_block+="- pane_mode: ${pane_mode:-}\n"
  metadata_block+="- alternate_screen_active: ${alternate_on:-0}\n"
  metadata_block+="- mouse_mode_active: ${mouse_any_flag:-0}\n"
  metadata_block+="- likely_tui_app: ${is_tui}\n"

  # If content is empty or too short, keep a placeholder
  if [[ ${#content} -lt 10 ]]; then
    content="(no terminal output captured)"
  fi
  
  # Truncate content if too long (to stay within token limits)
  if [[ ${#content} -gt 8000 ]]; then
    content="${content: -8000}"
  fi
  
  # Create a temporary file for the content to avoid escaping issues
  # mktemp works differently on macOS vs Linux
  local temp_file
  if [[ "$OSTYPE" == "darwin"* ]]; then
    temp_file=$(mktemp -t gw_tmp)
  else
    temp_file=$(mktemp)
  fi
  printf '%s' "$content" > "$temp_file"
  
  # Prepare the API request using Python for proper JSON encoding
  local request_body=""
  if have_cmd python3; then
    # Use Python to safely build the request body and extract typed commands from the capture
    request_payload=$(
      python3 - "$temp_file" "$history_arg" "$provider" "$openai_model" "$pane_cmd" "$window_name" "$pane_title" "$pane_mode" "$alternate_on" "$mouse_any_flag" "$is_tui" 2>/dev/null <<'PYCODE'
import json
import re
import sys
from pathlib import Path

temp_path = Path(sys.argv[1])
content = temp_path.read_text()
history_arg = sys.argv[2] if len(sys.argv) > 2 else ""
provider = sys.argv[3] if len(sys.argv) > 3 else "anthropic"
openai_model = sys.argv[4] if len(sys.argv) > 4 and sys.argv[4] else "gpt-4o-mini"
pane_cmd = sys.argv[5] if len(sys.argv) > 5 else ""
window_name = sys.argv[6] if len(sys.argv) > 6 else ""
pane_title = sys.argv[7] if len(sys.argv) > 7 else ""
pane_mode = sys.argv[8] if len(sys.argv) > 8 else ""
alternate_on = sys.argv[9] if len(sys.argv) > 9 else "0"
mouse_any_flag = sys.argv[10] if len(sys.argv) > 10 else "0"
is_tui = sys.argv[11] if len(sys.argv) > 11 else "false"

lines = content.splitlines()
command_candidates = []  # Commands captured with high confidence
fallback_candidates = []  # All prompt-like lines (used if we fail to classify)

history_commands = []
if history_arg:
    hist_path = Path(history_arg)
    if hist_path.exists():
        for line in hist_path.read_text().splitlines():
            line = line.strip()
            if not line:
                continue
            if "\t" in line:
                history_commands.append(line.split("\t", 1)[1])
            else:
                history_commands.append(line)
        history_commands = history_commands[-50:]

prompt_pattern = re.compile(r"^[A-Za-z0-9_.@~/-]+$")
prompt_sigil_pattern = re.compile(r"^(?P<prompt>[^\t]{0,120}?)(?P<sigil>[$#%])\s+(?P<cmd>.+)$")
simple_sigil_pattern = re.compile(r"^(?P<sigil>[$#%])\s+(?P<cmd>.+)$")
disallowed_prompts = {
    "warning",
    "error",
    "fatal",
    "hint",
    "note",
    "usage",
    "info",
    "debug",
    "trace",
}

disallowed_first_tokens = {
    "warning",
    "error",
    "fatal",
    "hint",
    "note",
    "usage",
    "not",
    "see",
    "for",
    "from",
    "with",
    "and",
    "or",
    "but",
    "at",
    "to",
    "in",
    "info",
    "debug",
    "trace",
}

common_commands = {
    "git",
    "gh",
    "npm",
    "pnpm",
    "yarn",
    "node",
    "npx",
    "bun",
    "cargo",
    "go",
    "python",
    "python3",
    "pip",
    "pip3",
    "poetry",
    "pipenv",
    "pytest",
    "uvicorn",
    "gunicorn",
    "flask",
    "django-admin",
    "rails",
    "bundle",
    "rake",
    "mix",
    "make",
    "cmake",
    "gradle",
    "mvn",
    "ant",
    "docker",
    "docker-compose",
    "kubectl",
    "helm",
    "terraform",
    "ansible",
    "ssh",
    "scp",
    "rsync",
    "sftp",
    "psql",
    "mysql",
    "mongo",
    "redis-cli",
    "sqlite3",
    "composer",
    "php",
    "ruby",
    "java",
    "javac",
    "deno",
    "dotnet",
    "msbuild",
    "tsc",
    "nx",
    "lerna",
    "eslint",
    "prettier",
    "ls",
    "cd",
    "pwd",
    "cat",
    "tail",
    "head",
    "less",
    "more",
    "grep",
    "rg",
    "fd",
    "find",
    "watch",
    "code",
    "open",
    "vim",
    "nvim",
    "tmux",
    "htop",
    "top",
    "brew",
    "tox",
}

allowed_prefixes = (
    "./",
    "../",
    "~/",
    "bin/",
    "sbin/",
)


def clean_tokens(tokens):
    while tokens and tokens[0] in {"$", "#", "%"}:
        tokens = tokens[1:]
    while tokens and (tokens[0].endswith("$") or tokens[0].endswith("#") or tokens[0].endswith("%")):
        tokens = tokens[1:]
    return tokens


def extract_prompt_and_command(raw_line):
    stripped = raw_line.strip()
    if not stripped:
        return "", ""

    # Pattern 1: host:path:command (common bash/zsh prompts)
    if ":" in stripped:
        maybe_prompt, maybe_cmd = stripped.split(":", 1)
        maybe_prompt = maybe_prompt.strip().rstrip("#$%")
        maybe_cmd = maybe_cmd.strip()
        if maybe_prompt and maybe_cmd and prompt_pattern.match(maybe_prompt):
            prompt_lower = maybe_prompt.lower()
            if prompt_lower not in disallowed_prompts:
                return maybe_prompt, maybe_cmd

    # Pattern 2: prompt ending with $, #, % (supports prompts without ':')
    match = prompt_sigil_pattern.match(stripped)
    if match:
        maybe_prompt = (match.group("prompt") or "").strip().rstrip("#$%")
        maybe_cmd = (match.group("cmd") or "").strip()
        if maybe_cmd:
            if maybe_prompt and maybe_prompt.lower() in disallowed_prompts:
                return "", ""
            return (maybe_prompt if maybe_prompt else match.group("sigil")), maybe_cmd

    # Pattern 3: bare prompt lines like "$ git status"
    match = simple_sigil_pattern.match(stripped)
    if match:
        maybe_cmd = (match.group("cmd") or "").strip()
        if maybe_cmd:
            return match.group("sigil"), maybe_cmd

    return "", ""


for raw_line in lines:
    prompt_part, command_part = extract_prompt_and_command(raw_line)
    if not command_part:
        continue

    tokens = clean_tokens(command_part.split())
    if not tokens:
        continue

    first_token = tokens[0].lower()
    if first_token == "sudo" and len(tokens) > 1:
        first_token = tokens[1].lower()

    if first_token in disallowed_first_tokens:
        continue

    normalized_command = " ".join(tokens)
    formatted_line = f"{prompt_part}:{normalized_command}"
    fallback_candidates.append(formatted_line)

    allowed = (
        first_token in common_commands
        or normalized_command.startswith(allowed_prefixes)
        or any(
            first_token.startswith(prefix)
            for prefix in (
                "git",
                "npm",
                "pnpm",
                "yarn",
                "node",
                "npx",
                "bun",
                "cargo",
                "python",
                "pip",
                "poetry",
                "pytest",
                "uvicorn",
                "docker",
                "kubectl",
                "helm",
                "terraform",
                "ansible",
                "ssh",
                "scp",
                "rsync",
                "rails",
                "bundle",
                "rake",
                "mix",
                "psql",
                "mysql",
                "mongo",
                "redis",
            )
        )
    )

    if allowed:
        command_candidates.append(formatted_line)


if history_commands:
    command_candidates = history_commands
elif not command_candidates:
    command_candidates = fallback_candidates

extracted_commands = command_candidates
history_lines = [f"{idx + 1}. {line}" for idx, line in enumerate(extracted_commands)]
command_history = "\n".join(history_lines) if history_lines else "None detected."
last_command_line = extracted_commands[-1] if extracted_commands else ""
commands_detected = bool(extracted_commands)

# Capture the first 10 lines of output as fallback context (joined by newline)
first_ten_lines = "\n".join(lines[:10]) if lines else ""

description_guidance = """Description rules:
- Describe the primary activity in a short phrase (3-8 words).
- Do NOT include any app/tool name or prefix in the output.
- If git activity is visible, the description should reflect it (e.g., reviewing changes), but do not add 'git_'."""

metadata_summary = "\n".join(
    line
    for line in [
        f"pane_current_command: {pane_cmd}" if pane_cmd else "",
        f"window_name: {window_name}" if window_name else "",
        f"pane_title: {pane_title}" if pane_title else "",
        f"pane_mode: {pane_mode}" if pane_mode else "",
        f"alternate_screen_active: {alternate_on}",
        f"mouse_mode_active: {mouse_any_flag}",
        f"likely_tui_app: {is_tui}",
    ]
    if line
)

prompt = f"""Analyze this terminal session and create a descriptive tmux session name based on what the user actually did.

Terminal output (last capture):
{content}

Tmux metadata (use as supporting hints, not as the primary source of truth):
{metadata_summary}

Extracted command history (oldest to newest):
{command_history}

Most recent command (anchor for description and summary):
{last_command_line if last_command_line else 'None detected'}

Focus: produce a concise description of the activity. Follow these rules:
{description_guidance}

{'No clear prompt/command lines were detected. Rely on the terminal output context.' if not commands_detected else ''}

{'An interactive TUI is likely running. Prefer the visible screen content plus recent command history over generic app names.' if is_tui.lower() in {'1', 'true', 'yes', 'on'} else ''}

{'First 10 lines of terminal output (useful for identifying running app/service):\n' + first_ten_lines if not commands_detected and first_ten_lines else ''}

If no commands were detected, fall back to terminal output analysis but describe the main activity in the name. Identify the dominant application or process implied by the output (e.g., web server, test runner, build tool).

Instructions:
1. Produce a session name using lowercase letters, numbers, and underscores only.
2. Maximum length: 100 characters.
3. Do NOT include any app/tool name or prefix.
4. Describe the concrete task or state in a short phrase.
5. If git activity is visible anywhere, the description must reflect git activity.
6. Respond with ONLY the final session name, nothing else."""

if provider == "openai":
    request = {
        "model": openai_model,
        "max_tokens": 100,
        "temperature": 0.2,
        "messages": [
            {
                "role": "user",
                "content": prompt
            }
        ]
    }
else:
    request = {
        "model": "claude-3-5-haiku-latest",
        "max_tokens": 100,
        "messages": [
            {
                "role": "user",
                "content": prompt
            }
        ]
    }

print(json.dumps(request))
PYCODE
    )
    request_body="$request_payload"
  fi
  if [[ -z "$request_body" ]]; then
    # Fallback: base64 encode the content to avoid escaping issues
    local encoded_content
    local fallback_file
    if [[ "$OSTYPE" == "darwin"* ]]; then
      fallback_file=$(mktemp -t gw_fb)
    else
      fallback_file=$(mktemp)
    fi
    {
      printf '%b\n\n' "$metadata_block"
      cat "$temp_file"
    } > "$fallback_file"
    encoded_content=$(base64 < "$fallback_file" | tr -d '\n')
    rm -f "$fallback_file"
    if [[ "$provider" == "openai" ]]; then
      request_body=$(cat <<EOF
{
  "model": "$openai_model",
  "max_tokens": 100,
  "temperature": 0.2,
  "messages": [
    {
      "role": "user",
      "content": "I'll send you base64-encoded terminal output. Please decode it, analyze it, and create a descriptive name for this tmux session.\n\nIMPORTANT: Use terminal metadata only as supporting hints. Prioritize the visible terminal content and recent command history.\n\nCRITICAL: Check for Git patterns FIRST:\n- Git commands: git status, diff, add, commit, push, pull, checkout, branch, merge, rebase, log, stash\n- Git output: 'On branch', 'Changes not staged', 'modified:', 'new file:', diff output (+/-/@@), commit hashes\n- If ANY Git patterns found, the description should reflect git activity (but do not add 'git_').\n\nDESCRIPTION ONLY: Return a short description of the activity.\n- Do NOT include the app/tool name or any prefix in the output.\n\nUse underscores only. Max 100 chars total.\n\nExamples:\n- reviewing_changes_before_commit\n- resolving_merge_conflicts\n- fixing_auth_bug\n- running_nextjs_dev_server\n\nBase64 terminal output:\n${encoded_content}\n\nRespond with ONLY the session name, nothing else."
    }
  ]
}
EOF
      )
    else
      request_body=$(cat <<EOF
{
  "model": "claude-3-5-haiku-latest",
  "max_tokens": 100,
  "messages": [
    {
      "role": "user",
      "content": "I'll send you base64-encoded terminal output. Please decode it, analyze it, and create a descriptive name for this tmux session.\n\nIMPORTANT: Use terminal metadata only as supporting hints. Prioritize the visible terminal content and recent command history.\n\nCRITICAL: Check for Git patterns FIRST:\n- Git commands: git status, diff, add, commit, push, pull, checkout, branch, merge, rebase, log, stash\n- Git output: 'On branch', 'Changes not staged', 'modified:', 'new file:', diff output (+/-/@@), commit hashes\n- If ANY Git patterns found, the description should reflect git activity (but do not add 'git_').\n\nDESCRIPTION ONLY: Return a short description of the activity.\n- Do NOT include the app/tool name or any prefix in the output.\n\nUse underscores only. Max 100 chars total.\n\nExamples:\n- reviewing_changes_before_commit\n- resolving_merge_conflicts\n- fixing_auth_bug\n- running_nextjs_dev_server\n\nBase64 terminal output:\n${encoded_content}\n\nRespond with ONLY the session name, nothing else."
    }
  ]
}
EOF
      )
    fi
  fi
  
  # Make the API call
  local response
  local new_name=""
  if [[ "$provider" == "openai" ]]; then
    # First attempt: OpenAI Responses API + Files API for structured app/description output.
    # If this path fails, we fall back to chat completions.
    local context_file=""
    if [[ "$OSTYPE" == "darwin"* ]]; then
      context_file=$(mktemp -t gw_ctx)
    else
      context_file=$(mktemp)
    fi
    {
      printf '%b\n\n' "$metadata_block"
      printf 'TMUX SESSION CAPTURE\n\n'
      cat "$temp_file"
      if [[ -n "$history_arg" && -f "$history_arg" ]]; then
        printf '\n\nRECENT COMMAND HISTORY\n\n'
        cat "$history_arg"
      fi
    } > "$context_file"

    local file_upload_resp openai_file_id
    file_upload_resp=$(curl -s -X POST https://api.openai.com/v1/files \
      --max-time 20 \
      -H "Authorization: Bearer $OPENAI_API_KEY" \
      -F purpose="user_data" \
      -F "file=@$context_file;type=text/plain" 2>/dev/null)
    rm -f "$context_file"

    openai_file_id=""
    if have_cmd python3; then
      openai_file_id="$(python3 -c 'import json,sys
try:
    data = json.load(sys.stdin)
except Exception:
    data = {}
print(data.get("id", ""))' <<<"$file_upload_resp" 2>/dev/null || true)"
    fi

    if [[ -n "$openai_file_id" ]]; then
      local responses_body responses_resp ai_desc
      responses_body=$(python3 - "$openai_model" "$openai_file_id" <<'PYCODE'
import json
import sys

model = sys.argv[1]
file_id = sys.argv[2]

prompt = """Analyze the provided terminal session file and produce ONLY a short activity description.

Return STRICT JSON in this exact shape:
{"description":"<short_snake_case_description>"}

Rules:
- description: concise snake_case description of the activity
- do NOT include app/tool name or any prefix in the description
- do not include punctuation beyond underscores
- keep it short and specific
"""

req = {
    "model": model,
    "temperature": 0.2,
    "max_output_tokens": 180,
    "input": [
        {
            "role": "user",
            "content": [
                {"type": "input_text", "text": prompt},
                {"type": "input_file", "file_id": file_id},
            ],
        }
    ],
}
print(json.dumps(req))
PYCODE
)

      responses_resp=$(curl -s -X POST https://api.openai.com/v1/responses \
        --max-time 20 \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer $OPENAI_API_KEY" \
        -d "$responses_body" 2>/dev/null)

      # Best-effort cleanup of uploaded file
      curl -s -X DELETE "https://api.openai.com/v1/files/$openai_file_id" \
        -H "Authorization: Bearer $OPENAI_API_KEY" >/dev/null 2>&1 || true

      ai_desc="$(python3 -c 'import json
import re
import sys

def clean(value):
    value = (value or "").strip().lower()
    value = re.sub(r"[^a-z0-9_ -]+", "", value)
    value = value.replace(" ", "_")
    value = re.sub(r"_+", "_", value).strip("_")
    return value

try:
    data = json.load(sys.stdin)
except Exception:
    print("")
    raise SystemExit

text = ""
if isinstance(data.get("output_text"), str):
    text = data["output_text"]

if not text:
    for item in data.get("output", []):
        if not isinstance(item, dict):
            continue
        if item.get("type") != "message":
            continue
        for content in item.get("content", []):
            if not isinstance(content, dict):
                continue
            if isinstance(content.get("text"), str):
                text += content["text"]

obj = None
if text:
    text = text.strip()
    try:
        obj = json.loads(text)
    except Exception:
        match = re.search(r"\{.*\}", text, re.S)
        if match:
            try:
                obj = json.loads(match.group(0))
            except Exception:
                obj = None

description = ""
if isinstance(obj, dict):
    description = clean(str(obj.get("description", "")))

if not description and text:
    description = clean(text)

print(description)' <<<"$responses_resp" 2>/dev/null || true)"

      if [[ -n "$ai_desc" ]]; then
        new_name="$ai_desc"
      fi
    fi

    if [[ -z "$new_name" ]]; then
      # Fallback: existing chat completions path
      response=$(curl -s -X POST https://api.openai.com/v1/chat/completions \
        --max-time 20 \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer $OPENAI_API_KEY" \
        -d "$request_body" 2>&1) || {
        err "Failed to call OpenAI API: $response"
        return 1
      }

      if echo "$response" | grep -q '"error"'; then
        local error_msg=$(echo "$response" | python3 -c "import sys, json; print(json.load(sys.stdin).get('error', {}).get('message', 'Unknown error'))" 2>/dev/null || echo "API error")
        err "API error: $error_msg"
        return 1
      fi

      new_name=$(echo "$response" | python3 -c "import sys, json; data = json.load(sys.stdin); print(data.get('choices', [{}])[0].get('message', {}).get('content', ''))" 2>/dev/null) || {
        new_name=$(echo "$response" | grep -o '"content":"[^"]*"' | head -1 | sed 's/"content":"\([^"]*\)"/\1/')
      }
    fi
  else
    response=$(curl -s -X POST https://api.anthropic.com/v1/messages \
      --max-time 20 \
      -H "Content-Type: application/json" \
      -H "x-api-key: $ANTHROPIC_API_KEY" \
      -H "anthropic-version: 2023-06-01" \
      -d "$request_body" 2>&1) || {
      err "Failed to call Anthropic API: $response"
      return 1
    }
    
    if echo "$response" | grep -q '"error"'; then
      local error_msg=$(echo "$response" | python3 -c "import sys, json; print(json.load(sys.stdin).get('error', {}).get('message', 'Unknown error'))" 2>/dev/null || echo "API error")
      err "API error: $error_msg"
      return 1
    fi
    
    new_name=$(echo "$response" | python3 -c "import sys, json; data = json.load(sys.stdin); print(data.get('content', [{}])[0].get('text', ''))" 2>/dev/null) || {
      new_name=$(echo "$response" | grep -o '"text":"[^"]*"' | head -1 | sed 's/"text":"\([^"]*\)"/\1/')
    }
  fi
  
  # Clean up temp files
  rm -f "$temp_file"
  if [[ -n "$history_temp" ]]; then
    rm -f "$history_temp"
  fi

  # Clean up the AI description (remove any spaces, special chars except underscore and dash)
  new_name=$(echo "$new_name" | tr ' ' '_' | sed 's/[^a-zA-Z0-9_-]//g' | cut -c1-100)
  if [[ -z "$new_name" ]]; then
    err "Failed to generate name from AI response"
    return 1
  fi

  new_name="${new_name:0:100}"
  
  echo "$new_name"
}

# Display tmux message
# Usage: tmux_display_message <message> [duration_ms]
tmux_display_message() {
  local message="$1"
  local duration="${2:-3000}"
  
  if tmux_available && tmux_inside_session; then
    tmux display-message -d "$duration" "$message"
  fi
}
