#!/usr/bin/env bash
#clear
#export PS4='\e[90m+${LINENO} in ${#BASH_SOURCE[@]}>${FUNCNAME[0]}:${BASH_SOURCE[@]##*/} \e[0m'
#set -x

#echo "starting: $0 <LOG_LEVEL=$1>"

### new.method

# ============================================================================
# otmux - tmux wrapper for oosh
# Makes tmux commands easy to use with intuitive method names
# ============================================================================

# Always use UTF-8 mode - essential for Claude Code and modern terminal apps
# The -u flag forces tmux to assume UTF-8 is supported, regardless of locale
TMUX_CMD="tmux -u"

# Configuration file location (symlinked to ~/.tmux.conf)
# Features: mouse support, macOS clipboard (pbcopy/pbpaste), vi keys
OTMUX_CONFIG="${OOSH_DIR:-$(dirname "$0")}/tmux.conf"

# Ensure color support for Claude Code and other modern CLI apps
# FORCE_COLOR=1 is needed because NO_COLOR might be set elsewhere
export FORCE_COLOR=2 
unset  COLORTERM 
#export COLORTERM=truecolor   # truecolor is not supported on mac just 256 colors

# ─────────────────────────────────────────────────────────────────────────────
# TARGET COMPLETION HELPERS
# ─────────────────────────────────────────────────────────────────────────────
# Targets in tmux follow these patterns:
#   Session:  session_name or $sessionId (e.g., "dev" or "$1")
#   Window:   [session:]window (e.g., "dev:0", "dev:main", or just "2")
#   Pane:     [session:][window.]pane (e.g., "dev:0.1", "0.2", or just "1")

private.resolve.target() {
  # Resolve direction targets (U/D/L/R) to pane IDs
  local target="$1"
  case "$target" in
    U|u|up)    $TMUX_CMD display-message -p -t "{up-of}" "#{pane_id}" 2>/dev/null || echo "$target" ;;
    D|d|down)  $TMUX_CMD display-message -p -t "{down-of}" "#{pane_id}" 2>/dev/null || echo "$target" ;;
    L|l|left)  $TMUX_CMD display-message -p -t "{left-of}" "#{pane_id}" 2>/dev/null || echo "$target" ;;
    R|r|right) $TMUX_CMD display-message -p -t "{right-of}" "#{pane_id}" 2>/dev/null || echo "$target" ;;
    *)         echo "$target" ;;
  esac
}

private.complete.sessions() {
  $TMUX_CMD list-sessions -F "#{session_name}" 2>/dev/null
}

private.complete.windows() {
  $TMUX_CMD list-windows -a -F "#{session_name}:#{window_index}:#{window_name}" 2>/dev/null | \
    while IFS=: read -r sess idx name; do
      echo "$sess:$idx"
      echo "$sess:$name"
    done
}

private.complete.panes() {
  $TMUX_CMD list-panes -a -F "#{session_name}:#{window_index}.#{pane_index}" 2>/dev/null
}

private.complete.buffers() {
  $TMUX_CMD list-buffers -F "#{buffer_name}" 2>/dev/null
}

private.complete.clients() {
  $TMUX_CMD list-clients -F "#{client_name}" 2>/dev/null
}

# ─────────────────────────────────────────────────────────────────────────────
# SESSION COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.new() # <?name> <?command> # create a new session
{
  local isFirstSession=false
  $TMUX_CMD list-sessions &>/dev/null || isFirstSession=true

  if [ -n "$1" ]; then
    local name="$1"
    shift
    $TMUX_CMD new-session -d -s "$name" "$@"
  else
    $TMUX_CMD new-session -d "$@"
  fi

  # Apply defaults on first session (server just started)
  if [ "$isFirstSession" = true ]; then
    otmux.setup.default
  fi

  # Attach to the new session
  if [ -n "$name" ]; then
    $TMUX_CMD attach-session -t "$name"
  else
    $TMUX_CMD attach-session
  fi
}

otmux.attach() # <?target> # attach to session; target=session name or $id
{
  if [ -n "$1" ]; then
    $TMUX_CMD attach-session -t "$1"
  else
    $TMUX_CMD attach-session
  fi
}
otmux.attach.completion.target() { private.complete.sessions; }

otmux.a() # <?session> # shorthand for attach
{
  otmux.attach "$@"
}

otmux.detach() # # detach from current session
{
  $TMUX_CMD detach-client "$@"
}

otmux.d() # # shorthand for detach
{
  otmux.detach "$@"
}

otmux.sessions() # # list all sessions
{
  $TMUX_CMD list-sessions "$@"
}

otmux.ls() # # shorthand for sessions
{
  otmux.sessions "$@"
}

otmux.has() # <target> # check if session exists; target=session name
{
  $TMUX_CMD has-session -t "$1" 2>/dev/null
}
otmux.has.completion.target() { private.complete.sessions; }

otmux.kill() # <target> # kill session; target=session name to destroy
{
  if [ -n "$1" ]; then
    $TMUX_CMD kill-session -t "$1"
  else
    error.log "usage: otmux kill <session-name>"
    return 1
  fi
}
otmux.kill.completion.target() { private.complete.sessions; }

private.otmux.killAll() # # kill all sessions except current
{
  $TMUX_CMD kill-session -a "$@"
}

otmux.rename() # <newName> # rename current session
{
  if [ -n "$1" ]; then
    $TMUX_CMD rename-session "$1"
  else
    error.log "usage: otmux rename <new-name>"
    return 1
  fi
}

otmux.switch() # <target> # switch to session; target=session name or $id
{
  if [ -n "$1" ]; then
    $TMUX_CMD switch-client -t "$1"
  else
    $TMUX_CMD switch-client "$@"
  fi
}
otmux.switch.completion.target() { private.complete.sessions; }

otmux.last() # # switch to last session
{
  $TMUX_CMD switch-client -l
}

otmux.next() # # switch to next session
{
  $TMUX_CMD switch-client -n
}

otmux.prev() # # switch to previous session
{
  $TMUX_CMD switch-client -p
}

otmux.lock() # # lock the current session
{
  $TMUX_CMD lock-session "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# WINDOW COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

private.otmux.newWindow() # <?name> <?command> # create a new window
{
  if [ -n "$1" ]; then
    $TMUX_CMD new-window -n "$1" "${@:2}"
  else
    $TMUX_CMD new-window "$@"
  fi
}

otmux.nw() # <?name> <?command> # shorthand for newWindow
{
  private.otmux.newWindow "$@"
}

otmux.windows() # # list windows in current session
{
  $TMUX_CMD list-windows "$@"
}

otmux.lsw() # # shorthand for windows
{
  otmux.windows "$@"
}

private.otmux.selectWindow() # <target> # select window; target=index (0,1,2) or name
{
  if [ -n "$1" ]; then
    $TMUX_CMD select-window -t "$1"
  else
    error.log "usage: otmux selectWindow <window>"
    return 1
  fi
}
private.otmux.selectWindow.completion.target() { private.complete.windows; }

otmux.sw() # <window> # shorthand for selectWindow
{
  private.otmux.selectWindow "$@"
}

private.otmux.nextWindow() # # go to next window
{
  $TMUX_CMD next-window
}

private.otmux.prevWindow() # # go to previous window
{
  $TMUX_CMD previous-window
}

private.otmux.lastWindow() # # go to last window
{
  $TMUX_CMD last-window
}

private.otmux.killWindow() # <?target> # kill window; target=[session:]window index/name
{
  if [ -n "$1" ]; then
    $TMUX_CMD kill-window -t "$1"
  else
    $TMUX_CMD kill-window
  fi
}
private.otmux.killWindow.completion.target() { private.complete.windows; }

otmux.kw() # <?window> # shorthand for killWindow
{
  private.otmux.killWindow "$@"
}

private.otmux.renameWindow() # <target|name> <?name> # rename window; 2 args = target + name, 1 arg = name only
{
  if [ $# -ge 2 ]; then
    $TMUX_CMD rename-window -t "$1" "$2"
  elif [ -n "$1" ]; then
    $TMUX_CMD rename-window "$1"
  else
    error.log "usage: otmux renameWindow <new-name> OR otmux renameWindow <target> <new-name>"
    return 1
  fi
}

otmux.rw() # <newName> # shorthand for renameWindow
{
  private.otmux.renameWindow "$@"
}

private.otmux.moveWindow() # <target> # move window; target=destination [session:]index
{
  $TMUX_CMD move-window "$@"
}
private.otmux.moveWindow.completion.target() { private.complete.windows; }

private.otmux.swapWindow() # <target> # swap with window; target=[session:]window to swap with
{
  $TMUX_CMD swap-window "$@"
}
private.otmux.swapWindow.completion.target() { private.complete.windows; }

private.otmux.linkWindow() # <src> <dst> # link window; src=source window, dst=target session
{
  $TMUX_CMD link-window "$@"
}
private.otmux.linkWindow.completion.src() { private.complete.windows; }
private.otmux.linkWindow.completion.dst() { private.complete.sessions; }

private.otmux.unlinkWindow() # <?target> # unlink window; target=window to unlink
{
  $TMUX_CMD unlink-window "$@"
}
private.otmux.unlinkWindow.completion.target() { private.complete.windows; }

private.otmux.findWindow() # <pattern> # find window by pattern
{
  if [ -n "$1" ]; then
    $TMUX_CMD find-window "$1"
  else
    error.log "usage: otmux findWindow <pattern>"
    return 1
  fi
}

private.otmux.rotateWindow() # # rotate panes in window
{
  $TMUX_CMD rotate-window "$@"
}

private.otmux.respawnWindow() # <?command> # respawn window with command
{
  $TMUX_CMD respawn-window "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# PANE COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.split() # <?command> # split pane horizontally
{
  $TMUX_CMD split-window "$@"
}

private.otmux.splitH() # <?command> # split pane horizontally (alias)
{
  $TMUX_CMD split-window -h "$@"
}

otmux.split.h() # <?command> # split pane horizontally (side-by-side)
{
  private.otmux.splitH "$@"
}

private.otmux.splitV() # <?command> # split pane vertically
{
  $TMUX_CMD split-window -v "$@"
}

otmux.split.v() # <?command> # split pane vertically (top-bottom)
{
  private.otmux.splitV "$@"
}

otmux.panes() # # list panes in current window
{
  $TMUX_CMD list-panes "$@"
}

otmux.lsp() # # shorthand for panes
{
  otmux.panes "$@"
}

private.otmux.selectPane() # <target> # select pane; target=U/D/L/R direction or index (0,1,2)
{
  case "$1" in
    U|u|up)    $TMUX_CMD select-pane -U ;;
    D|d|down)  $TMUX_CMD select-pane -D ;;
    L|l|left)  $TMUX_CMD select-pane -L ;;
    R|r|right) $TMUX_CMD select-pane -R ;;
    *)         $TMUX_CMD select-pane -t "$1" ;;
  esac
}


otmux.sp() # <direction> # shorthand for selectPane
{
  private.otmux.selectPane "$@"
}

otmux.up() # # select pane above
{
  $TMUX_CMD select-pane -U
}

otmux.down() # # select pane below
{
  $TMUX_CMD select-pane -D
}

otmux.left() # # select pane to the left
{
  $TMUX_CMD select-pane -L
}

otmux.right() # # select pane to the right
{
  $TMUX_CMD select-pane -R
}

private.otmux.lastPane() # # select last pane
{
  $TMUX_CMD last-pane
}

private.otmux.killPane() # <?target> # kill pane; target=[session:][window.]pane index
{
  if [ -n "$1" ]; then
    $TMUX_CMD kill-pane -t "$1"
  else
    $TMUX_CMD kill-pane
  fi
}


otmux.kp() # <?pane> # shorthand for killPane
{
  private.otmux.killPane "$@"
}

private.otmux.breakPane() # # break pane into a new window
{
  $TMUX_CMD break-pane "$@"
}

private.otmux.joinPane() # <window> # join pane to window; window=destination [session:]window
{
  $TMUX_CMD join-pane "$@"
}

private.otmux.swapPane() # <sourcePane> <targetPane> # swap two panes
{
  local sourcePane=$(private.resolve.target "$1")
  local targetPane=$(private.resolve.target "$2")
  if [ -z "$sourcePane" ] || [ -z "$targetPane" ]; then
    error.log "usage: otmux pane.swap <sourcePane> <targetPane>"
    return 1
  fi
  $TMUX_CMD swap-pane -s "$sourcePane" -t "$targetPane"
}

private.otmux.movePane() # <window> # move pane; window=destination window
{
  $TMUX_CMD move-pane "$@"
}

private.otmux.resizePane() # <direction> <?amount> # resize pane; direction=U/D/L/R, amount=cells (default 5)
{
  local amount="${2:-5}"
  case "$1" in
    U|u|up)    $TMUX_CMD resize-pane -U "$amount" ;;
    D|d|down)  $TMUX_CMD resize-pane -D "$amount" ;;
    L|l|left)  $TMUX_CMD resize-pane -L "$amount" ;;
    R|r|right) $TMUX_CMD resize-pane -R "$amount" ;;
    *)         $TMUX_CMD resize-pane "$@" ;;
  esac
}

otmux.rp() # <direction> <?amount> # shorthand for resizePane
{
  private.otmux.resizePane "$@"
}

otmux.zoom() # # toggle pane zoom
{
  $TMUX_CMD resize-pane -Z
}

otmux.z() # # shorthand for zoom
{
  otmux.zoom
}

private.otmux.respawnPane() # <?command> # respawn pane with command
{
  $TMUX_CMD respawn-pane "$@"
}

private.otmux.pipePane() # <?command> # pipe pane output to command
{
  $TMUX_CMD pipe-pane "$@"
}

private.otmux.displayPanes() # # display pane numbers
{
  $TMUX_CMD display-panes "$@"
}

otmux.dp() # # shorthand for displayPanes
{
  private.otmux.displayPanes "$@"
}

private.otmux.capturePane() # <?options> # capture pane contents
{
  $TMUX_CMD capture-pane "$@"
}

private.otmux.clearHistory() # # clear pane history
{
  $TMUX_CMD clear-history "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# LAYOUT COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.layout() # <layout> <?target> # set window layout; pass all args through
{
  $TMUX_CMD select-layout "$@"
}

private.otmux.evenH() # # set even-horizontal layout
{
  $TMUX_CMD select-layout even-horizontal
}

private.otmux.evenV() # # set even-vertical layout
{
  $TMUX_CMD select-layout even-vertical
}

private.otmux.mainH() # # set main-horizontal layout
{
  $TMUX_CMD select-layout main-horizontal
}

private.otmux.mainV() # # set main-vertical layout
{
  $TMUX_CMD select-layout main-vertical
}

otmux.tiled() # <?target> # set tiled layout; target=optional window target
{
  $TMUX_CMD select-layout ${1:+-t "$1"} tiled
}

private.otmux.nextLayout() # # cycle to next layout
{
  $TMUX_CMD next-layout
}

private.otmux.prevLayout() # # cycle to previous layout
{
  $TMUX_CMD previous-layout
}

# ─────────────────────────────────────────────────────────────────────────────
# BUFFER COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.buffers() # # list buffers
{
  $TMUX_CMD list-buffers "$@"
}

otmux.lsb() # # shorthand for buffers
{
  otmux.buffers "$@"
}

private.otmux.showBuffer() # <?buffer> # show buffer contents; buffer=buffer name
{
  $TMUX_CMD show-buffer "$@"
}
private.otmux.setBuffer() # <data> # set buffer data
{
  if [ -n "$1" ]; then
    $TMUX_CMD set-buffer "$1"
  else
    error.log "usage: otmux setBuffer <data>"
    return 1
  fi
}

private.otmux.loadBuffer() # <file> # load buffer from file
{
  if [ -n "$1" ]; then
    $TMUX_CMD load-buffer "$1"
  else
    error.log "usage: otmux loadBuffer <file>"
    return 1
  fi
}

private.otmux.saveBuffer() # <file> # save buffer to file
{
  if [ -n "$1" ]; then
    $TMUX_CMD save-buffer "$1"
  else
    error.log "usage: otmux saveBuffer <file>"
    return 1
  fi
}

private.otmux.deleteBuffer() # <?buffer> # delete buffer; buffer=buffer name
{
  $TMUX_CMD delete-buffer "$@"
}
private.otmux.pasteBuffer() # # paste buffer contents
{
  $TMUX_CMD paste-buffer "$@"
}

otmux.paste() # # shorthand for pasteBuffer
{
  private.otmux.pasteBuffer "$@"
}

private.otmux.chooseBuffer() # # interactively choose a buffer
{
  $TMUX_CMD choose-buffer "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# CLIENT COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.clients() # # list clients
{
  $TMUX_CMD list-clients "$@"
}

otmux.lsc() # # shorthand for clients
{
  otmux.clients "$@"
}

private.otmux.detachClient() # <?client> # detach client; client=client tty (e.g., /dev/pts/0)
{
  $TMUX_CMD detach-client "$@"
}
private.otmux.suspendClient() # # suspend current client
{
  $TMUX_CMD suspend-client "$@"
}

private.otmux.refreshClient() # # refresh client display
{
  $TMUX_CMD refresh-client "$@"
}

private.otmux.chooseClient() # # interactively choose a client
{
  $TMUX_CMD choose-client "$@"
}

private.otmux.chooseTree() # # interactively choose session/window
{
  $TMUX_CMD choose-tree "$@"
}

private.otmux.lockClient() # # lock the client
{
  $TMUX_CMD lock-client "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# SERVER COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

private.otmux.startServer() # # start the tmux server
{
  $TMUX_CMD start-server
}

private.otmux.killServer() # # kill the tmux server
{
  $TMUX_CMD kill-server
}

private.otmux.lockServer() # # lock all clients
{
  $TMUX_CMD lock-server
}

otmux.info() # # show server information
{
  $TMUX_CMD info
}

# ─────────────────────────────────────────────────────────────────────────────
# CONFIGURATION COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.source() # <?file> # source a config file (defaults to otmux config)
{
  if [ -n "$1" ]; then
    $TMUX_CMD source-file "$1"
  elif [ -f "$OTMUX_CONFIG" ]; then
    $TMUX_CMD source-file "$OTMUX_CONFIG"
    echo "Loaded: $OTMUX_CONFIG"
  else
    $TMUX_CMD source-file ~/.tmux.conf
  fi
}

otmux.reload() # # reload the otmux config (mouse, clipboard, etc.)
{
  if [ -f "$OTMUX_CONFIG" ]; then
    $TMUX_CMD source-file "$OTMUX_CONFIG"
    echo "Reloaded: $OTMUX_CONFIG"
    echo "Features: mouse support, macOS clipboard, vi keys, 256 colors"
  elif [ -f ~/.tmux.conf ]; then
    $TMUX_CMD source-file ~/.tmux.conf
    echo "Reloaded: ~/.tmux.conf"
  else
    error.log "No config file found"
    return 1
  fi
}

private.otmux.configPath() # # show the otmux config file location
{
  if [ -f "$OTMUX_CONFIG" ]; then
    echo "$OTMUX_CONFIG"
  else
    echo "~/.tmux.conf"
  fi
}

# Default tmux configuration template location
OTMUX_DEFAULT_CONFIG_TEMPLATE="${OOSH_DIR:-$(dirname "$0")}/templates/user/tmuxDefaultConfig"

# Output the default tmux configuration from external template
private.default.config() {
  if [ -f "$OTMUX_DEFAULT_CONFIG_TEMPLATE" ]; then
    cat "$OTMUX_DEFAULT_CONFIG_TEMPLATE"
  else
    error.log "Template not found: $OTMUX_DEFAULT_CONFIG_TEMPLATE"
    return 1
  fi
}

otmux.config.init() # # create default tmux config if not exists (with color support)
{
  local configFile="$HOME/.tmux.conf"
  local ooshLink="$OTMUX_CONFIG"
  
  # Check if config already exists
  if [ -f "$configFile" ]; then
    echo "Config already exists: $configFile"
    echo "Use 'otmux config.reset' to overwrite with defaults"
    return 0
  fi
  
  # Create the config file
  echo "Creating default tmux config: $configFile"
  private.default.config > "$configFile"
  
  # Create symlink in oosh directory if it doesn't exist
  if [ ! -e "$ooshLink" ] && [ -n "$ooshLink" ]; then
    ln -sf "$configFile" "$ooshLink" 2>/dev/null && \
      echo "Created symlink: $ooshLink -> $configFile"
  fi
  
  # Source the config if tmux server is running
  if $TMUX_CMD list-sessions &>/dev/null; then
    $TMUX_CMD source-file "$configFile"
    echo "Config loaded into running tmux server"
  fi
  
  echo ""
  echo "Features enabled:"
  echo "  - Mouse support (click, scroll, select)"
  echo "  - Pane title headers (agent names at top border)"
  echo "  - Color support (FORCE_COLOR=1, COLORTERM=truecolor)"
  echo "  - Vi-style copy mode"
  echo "  - 50,000 line scrollback"
  echo "  - Intuitive splits: prefix + | (horizontal), prefix + - (vertical)"
  echo ""
  echo "Run 'otmux setup.default' to apply clipboard and all defaults to running server"
}

otmux.config.reset() # # reset tmux config to default (overwrites existing)
{
  local configFile="$HOME/.tmux.conf"
  local ooshLink="$OTMUX_CONFIG"
  local backupFile="$configFile.backup.$(date +%Y%m%d_%H%M%S)"
  
  # Backup existing config if present
  if [ -f "$configFile" ] && [ ! -L "$configFile" ]; then
    cp "$configFile" "$backupFile"
    echo "Backed up existing config to: $backupFile"
  fi
  
  # Create/overwrite the config file
  echo "Resetting tmux config to defaults: $configFile"
  private.default.config > "$configFile"
  
  # Ensure symlink in oosh directory
  if [ -n "$ooshLink" ]; then
    rm -f "$ooshLink" 2>/dev/null
    ln -sf "$configFile" "$ooshLink" 2>/dev/null && \
      echo "Updated symlink: $ooshLink -> $configFile"
  fi
  
  # Source the config if tmux server is running
  if $TMUX_CMD list-sessions &>/dev/null; then
    $TMUX_CMD source-file "$configFile"
    echo "Config reloaded into running tmux server"
  fi
  
  echo ""
  echo "Config reset to defaults with color support for Claude Code"
}

# ─────────────────────────────────────────────────────────────────────────────
# SETUP — apply defaults to running server
# ─────────────────────────────────────────────────────────────────────────────

otmux.setup.default() # # apply sensible defaults to running server (pane headers, clipboard, mouse)
{
  # Pane title headers — show agent name at top, bold if active
  $TMUX_CMD set -g pane-border-status top
  $TMUX_CMD set -g pane-border-format " #{?pane_active,#[bold],}#T#[default] "

  # Mouse, vi keys, colors, scrollback
  $TMUX_CMD set -g mouse on
  $TMUX_CMD setw -g mode-keys vi
  $TMUX_CMD set -g default-terminal "xterm-256color"
  $TMUX_CMD set -ga terminal-overrides ",xterm-256color:Tc"
  $TMUX_CMD set -g history-limit 50000
  $TMUX_CMD set -g renumber-windows on

  # OS-independent clipboard
  private.otmux.setup.clipboard

  success.log "defaults applied to running server"
}

private.otmux.setup.clipboard() # # detect OS and configure clipboard bindings
{
  source "$OOSH_DIR/os"
  if os.check private.otmux.setup.clipboard; then
    $RESULT
  else
    important.log "clipboard: unsupported OS ($OSTYPE) — configure manually"
  fi
}

private.otmux.setup.clipboard.darwin() # # macOS clipboard via pbcopy/pbpaste
{
  local copyCmd="pbcopy"
  local pasteCmd="pbpaste"
  private.otmux.setup.clipboard.apply "$copyCmd" "$pasteCmd"
}

private.otmux.setup.clipboard.linux() # # Linux clipboard via xclip, xsel, or termux
{
  local copyCmd="" pasteCmd=""
  if command -v xclip &>/dev/null; then
    copyCmd="xclip -selection clipboard"
    pasteCmd="xclip -selection clipboard -o"
  elif command -v xsel &>/dev/null; then
    copyCmd="xsel --clipboard"
    pasteCmd="xsel --clipboard --output"
  elif command -v termux-clipboard-set &>/dev/null; then
    copyCmd="termux-clipboard-set"
    pasteCmd="termux-clipboard-get"
  else
    important.log "clipboard: no xclip, xsel, or termux-clipboard found — install one"
    return 1
  fi
  private.otmux.setup.clipboard.apply "$copyCmd" "$pasteCmd"
}

private.otmux.setup.clipboard.apply() # <copyCmd> <pasteCmd> # bind tmux copy/paste to OS clipboard
{
  local copyCmd="$1"
  local pasteCmd="$2"

  $TMUX_CMD set -g set-clipboard on
  $TMUX_CMD bind -T copy-mode-vi v send-keys -X begin-selection
  $TMUX_CMD bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "$copyCmd"
  $TMUX_CMD bind -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "$copyCmd"
  $TMUX_CMD bind -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "$copyCmd"
  $TMUX_CMD bind p run "$pasteCmd | tmux load-buffer - && tmux paste-buffer"

  info.log "clipboard: using $copyCmd / $pasteCmd"
}

otmux.install() # # install tmux via oo cmd and initialize config
{
  echo "Checking tmux installation..."
  
  # Use oo cmd to check and install tmux
  # oo cmd handles package manager detection via oo.pm.discover and $OOSH_PM
  oo cmd tmux
  
  # Verify installation
  if ! command -v tmux &>/dev/null; then
    error.log "tmux installation failed"
    return 1
  fi
  
  echo ""
  echo "tmux installed successfully: $(tmux -V)"
  echo ""
  
  # Initialize config
  otmux.config.init
}

otmux.set() # <option> <value> # set a tmux option
{
  $TMUX_CMD set-option "$@"
}

private.otmux.setGlobal() # <option> <value> # set a global option
{
  $TMUX_CMD set-option -g "$@"
}

private.otmux.setWindow() # <option> <value> # set a window option
{
  $TMUX_CMD set-window-option "$@"
}

otmux.show() # <?option> # show options
{
  $TMUX_CMD show-options "$@"
}

private.otmux.showGlobal() # <?option> # show global options
{
  $TMUX_CMD show-options -g "$@"
}

private.otmux.showWindow() # <?option> # show window options
{
  $TMUX_CMD show-window-options "$@"
}

private.otmux.showHooks() # # show hooks
{
  $TMUX_CMD show-hooks "$@"
}

private.otmux.setHook() # <hook> <command> # set a hook
{
  $TMUX_CMD set-hook "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# KEY BINDING COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.bind() # <key> <command> # bind a key
{
  $TMUX_CMD bind-key "$@"
}

otmux.unbind() # <key> # unbind a key
{
  $TMUX_CMD unbind-key "$@"
}

otmux.keys() # # list key bindings
{
  $TMUX_CMD list-keys "$@"
}

otmux.lsk() # # shorthand for keys
{
  otmux.keys "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# ENVIRONMENT COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

private.otmux.setEnv() # <name> <value> # set environment variable
{
  $TMUX_CMD set-environment "$@"
}

private.otmux.showEnv() # <?name> # show environment variables
{
  $TMUX_CMD show-environment "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# SEND/RUN COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.send() # <target> <keys> # send keys to pane; target=pane id, index, or direction (U/D/L/R)
{
  local target=$(private.resolve.target "$1")
  shift
  # Detect trailing Enter — add delay for TUI apps like Claude Code.
  # Without delay, the TUI receives Enter before processing the text.
  local last=""
  for last in "$@"; do :; done
  if [ "$last" = "Enter" ] && [ $# -gt 1 ]; then
    local textCount=$(($# - 1))
    $TMUX_CMD send-keys -t "$target" "${@:1:textCount}"
    sleep 0.05
    $TMUX_CMD send-keys -t "$target" Enter
  else
    $TMUX_CMD send-keys -t "$target" "$@"
  fi
}
otmux.send.key() # <target> <key> <?count:1> # send key N times to pane; key=Left/Right/Up/Down/Home/End/etc
{
  local target=$(private.resolve.target "$1")
  local key="$2"
  local count="${3:-1}"

  if [ -z "$target" ] || [ -z "$key" ]; then
    error.log "usage: otmux send.key <target> <key> <?count>"
    return 1
  fi

  $TMUX_CMD send-keys -t "$target" -N "$count" "$key"
}
otmux.send.key.completion.key() {
  echo "Left"
  echo "Right"
  echo "Up"
  echo "Down"
  echo "Home"
  echo "End"
  echo "BSpace"
  echo "DC"
  echo "Tab"
  echo "Enter"
}

otmux.parameter.completion.target() {
  echo "U"
  echo "D"
  echo "L"
  echo "R"
  private.complete.panes
}

otmux.parameter.completion.sourcePane() { private.complete.panes; }
otmux.parameter.completion.targetPane() { private.complete.panes; }
otmux.parameter.completion.session() { private.complete.sessions; }
otmux.parameter.completion.direction() {
  echo "U"
  echo "D"
  echo "L"
  echo "R"
}
otmux.parameter.completion.layout() {
  echo "even-horizontal"
  echo "even-vertical"
  echo "main-horizontal"
  echo "main-vertical"
  echo "tiled"
}
otmux.parameter.completion.pane() { private.complete.panes; }
otmux.parameter.completion.window() { private.complete.windows; }
otmux.parameter.completion.buffer() { private.complete.buffers; }
otmux.parameter.completion.client() { private.complete.clients; }

otmux.parameter.completion.option() {
  $TMUX_CMD show-options -g 2>/dev/null | cut -d' ' -f1
  $TMUX_CMD show-window-options -g 2>/dev/null | cut -d' ' -f1
}

otmux.parameter.completion.file() {
  compgen -f "$1"
}

otmux.send.verified() # <target> <text> <?timeout:3> # send text to pane and verify delivery via capture
{
    local target=$(private.resolve.target "$1")
    local text="$2"
    local timeout="${3:-3}"

    if [ -z "$target" ] || [ -z "$text" ]; then
        error.log "usage: otmux send.verified <target> <text> <?timeout>"
        return 1
    fi

    # Capture state BEFORE send
    local before
    before=$($TMUX_CMD capture-pane -t "$target" -p -S -5 2>/dev/null | tail -5)

    # Send text + Enter (using sendEnter for literal text handling)
    private.otmux.sendEnter "$target" "$text"

    # Wait for TUI to process
    sleep "$timeout"

    # Capture state AFTER send
    local after
    after=$($TMUX_CMD capture-pane -t "$target" -p -S -10 2>/dev/null | tail -10)

    # Verify: text should appear in pane OR pane state should have changed
    if [ "$before" = "$after" ]; then
        # Pane didn't change — likely blocked (permission dialog, overlay, etc.)
        error.log "send.verified FAILED: pane $target unchanged after send"
        RESULT="FAILED"
        return 1
    fi

    # Check if our text appears in the after capture
    if echo "$after" | grep -qF "$text"; then
        console.log "send.verified OK: text delivered to $target"
        RESULT="DELIVERED"
        return 0
    fi

    # Pane changed but text not visible — likely processed (agent consumed it)
    console.log "send.verified OK: pane $target changed (text likely processed)"
    RESULT="CHANGED"
    return 0
}


private.otmux.sendEnter() # <target> <text> # send text followed by Enter; target=pane or direction (U/D/L/R)
{
  local target=$(private.resolve.target "$1")
  shift
  # Send text literally (-l) to avoid interpreting tmux key names in the message,
  # then send Enter separately with a brief delay. This prevents words like
  # "Escape" or "Up" from being treated as key sequences, and improves reliability
  # with TUI prompts (Claude Code accept-edits, permission dialogs).
  if [ -n "$*" ]; then
    $TMUX_CMD send-keys -t "$target" -l "$*"
    sleep 0.05
  fi
  $TMUX_CMD send-keys -t "$target" Enter
}

private.otmux.sendKeys() # <target> <key1> [<key2>...] # send keys with inter-key delays for TUI apps
{
  # Sends each argument as a separate key with a brief delay between them.
  # Use this for TUI interactions where keys must be processed sequentially
  # (e.g. Claude Code permission prompts: sendKeys <pane> Down Enter).
  local target=$(private.resolve.target "$1")
  shift
  for key in "$@"; do
    $TMUX_CMD send-keys -t "$target" "$key"
    sleep 0.05
  done
}


otmux.run() # <command> # run shell command
{
  $TMUX_CMD run-shell "$@"
}

otmux.display() # <message> # display a message
{
  $TMUX_CMD display-message "$@"
}

otmux.msg() # <message> # shorthand for display
{
  otmux.display "$@"
}

otmux.confirm() # <command> # confirm before running command
{
  $TMUX_CMD confirm-before "$@"
}

otmux.prompt() # <?template> # command prompt
{
  $TMUX_CMD command-prompt "$@"
}

otmux.menu() # # display menu
{
  $TMUX_CMD display-menu "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# COPY MODE COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.copy() # # enter copy mode
{
  $TMUX_CMD copy-mode "$@"
}

otmux.clock() # # show clock
{
  $TMUX_CMD clock-mode "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# UTILITY COMMANDS
# ─────────────────────────────────────────────────────────────────────────────

otmux.wait() # <channel> # wait for a channel
{
  $TMUX_CMD wait-for "$@"
}

otmux.if() # <condition> <cmd1> <?cmd2> # conditional execution
{
  $TMUX_CMD if-shell "$@"
}

private.otmux.showMessages() # # show server messages
{
  $TMUX_CMD show-messages "$@"
}

otmux.commands() # # list all tmux commands
{
  $TMUX_CMD list-commands "$@"
}

otmux.lscm() # # shorthand for commands
{
  otmux.commands "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# GLOBAL FLAGS
# Note: UTF-8 mode (-u) is always enabled by default via TMUX_CMD
# ─────────────────────────────────────────────────────────────────────────────

otmux.256color() # <command> # force 256 color mode (stacks with UTF-8)
{
  tmux -u -2 "$@"
}

otmux.control() # <?command> # start in control mode (stacks with UTF-8)
{
  tmux -u -C "$@"
}

otmux.utf8() # <command> # force UTF-8 mode (already default, provided for compatibility)
{
  $TMUX_CMD "$@"
}

otmux.verbose() # <command> # enable verbose logging (stacks with UTF-8)
{
  tmux -u -v "$@"
}

otmux.socket() # <name> <command> # use specific socket name (stacks with UTF-8)
{
  local name="$1"
  shift
  tmux -u -L "$name" "$@"
}

private.otmux.socketPath() # <path> <command> # use specific socket path (stacks with UTF-8)
{
  local path="$1"
  shift
  tmux -u -S "$path" "$@"
}

otmux.config() # <file> <command> # use specific config file (stacks with UTF-8)
{
  local file="$1"
  shift
  tmux -u -f "$file" "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# STATUS & HELP
# ─────────────────────────────────────────────────────────────────────────────

otmux.version() # # show tmux version
{
  $TMUX_CMD -V
}

otmux.v() # # shorthand for version
{
  otmux.version
}

otmux.help() # # show tmux help
{
  $TMUX_CMD --help 2>&1 | head -50
  echo ""
  echo "For full documentation: man tmux"
}

otmux.tree() # <?session> # show formatted tree of all sessions, panes, titles and commands
{
  if ! $TMUX_CMD list-sessions >/dev/null 2>&1; then
    echo "No tmux server running. Use 'otmux new' to start."
    return 1
  fi

  local filter_session="$1"

  # Collect session info: name, attached status, creation date
  local sessionData
  if [ -n "$filter_session" ]; then
    sessionData=$($TMUX_CMD list-sessions -f "#{==:#{session_name},$filter_session}" \
      -F "#{session_name}|#{session_attached}|#{session_created}" 2>/dev/null)
  else
    sessionData=$($TMUX_CMD list-sessions \
      -F "#{session_name}|#{session_attached}|#{session_created}" 2>/dev/null)
  fi

  if [ -z "$sessionData" ]; then
    echo "No tmux sessions."
    return 0
  fi

  local sessionCount
  sessionCount=$(echo "$sessionData" | wc -l | tr -d ' ')

  echo "tmux sessions"
  echo "│"

  local sessionI=0

  echo "$sessionData" | while IFS='|' read -r sessName sessAttached sessCreated; do
    sessionI=$((sessionI + 1))
    local isLast=0
    [ "$sessionI" -eq "$sessionCount" ] && isLast=1

    # Format creation date (Mon DD or Mon D)
    local sessDate
    if date -r "$sessCreated" "+%b %-d" >/dev/null 2>&1; then
      sessDate=$(date -r "$sessCreated" "+%b %-d")
    else
      sessDate=$(date -d "@$sessCreated" "+%b %-d" 2>/dev/null || echo "unknown")
    fi

    # Session connector
    local sessConnector="├──"
    local panePrefix="│   "
    if [ "$isLast" -eq 1 ]; then
      sessConnector="└──"
      panePrefix="    "
    fi

    # Session label: name (attached, date) or name (date)
    local sessLabel="$sessName"
    if [ "$sessAttached" -gt 0 ] 2>/dev/null; then
      sessLabel="$sessName (attached, $sessDate)"
    else
      sessLabel="$sessName ($sessDate)"
    fi

    echo "$sessConnector $sessLabel"

    # Collect panes for this session
    local paneData
    paneData=$($TMUX_CMD list-panes -t "$sessName" -s \
      -F "#{window_index}|#{pane_index}|#{pane_title}|#{pane_current_command}" 2>/dev/null)

    local paneCount
    paneCount=$(echo "$paneData" | wc -l | tr -d ' ')
    local paneI=0

    echo "$paneData" | while IFS='|' read -r winIdx paneIdx paneTitle paneCmd; do
      paneI=$((paneI + 1))

      local address="${winIdx}.${paneIdx}"
      local title="${paneTitle:--}"

      # Pane connector
      local paneConnector="├──"
      [ "$paneI" -eq "$paneCount" ] && paneConnector="└──"

      # When paneCmd is bash/zsh, check for Claude child process and show version
      local displayCmd="$paneCmd"
      if [[ "$paneCmd" == "bash" || "$paneCmd" == "zsh" ]]; then
        local paneTarget="${sessName}:${winIdx}.${paneIdx}"
        if "$OOSH_DIR/claudeCode" process.running "$paneTarget" 2>/dev/null; then
          local ver
          ver=$("$OOSH_DIR/claudeCode" version "$paneTarget" 2>/dev/null)
          ver="${ver%% (*}"  # trim " (Claude Code)" suffix
          [ -n "$ver" ] && displayCmd="$ver"
        fi
      fi
      printf "%s%s %-5s %-24s [%s]\n" "$panePrefix" "$paneConnector" "$address" "$title" "$displayCmd"
    done

    echo "${panePrefix}"
  done
}

otmux.tree.detailed() # <?session> # show three-level tree with Claude session IDs per pane
{
  if ! $TMUX_CMD list-sessions >/dev/null 2>&1; then
    echo "No tmux server running. Use 'otmux new' to start."
    return 1
  fi

  local filter_session="$1"

  local sessionData
  if [ -n "$filter_session" ]; then
    sessionData=$($TMUX_CMD list-sessions -f "#{==:#{session_name},$filter_session}" \
      -F "#{session_name}|#{session_attached}|#{session_created}" 2>/dev/null)
  else
    sessionData=$($TMUX_CMD list-sessions \
      -F "#{session_name}|#{session_attached}|#{session_created}" 2>/dev/null)
  fi

  if [ -z "$sessionData" ]; then
    echo "No tmux sessions."
    return 0
  fi

  local sessionCount
  sessionCount=$(echo "$sessionData" | wc -l | tr -d ' ')
  local ooshDir="${OOSH_DIR:-$(dirname "$0")}"

  echo "tmux sessions"
  echo "│"

  local sessionI=0

  echo "$sessionData" | while IFS='|' read -r sessName sessAttached sessCreated; do
    sessionI=$((sessionI + 1))
    local isLast=0
    [ "$sessionI" -eq "$sessionCount" ] && isLast=1

    local sessDate
    if date -r "$sessCreated" "+%b %-d" >/dev/null 2>&1; then
      sessDate=$(date -r "$sessCreated" "+%b %-d")
    else
      sessDate=$(date -d "@$sessCreated" "+%b %-d" 2>/dev/null || echo "unknown")
    fi

    local sessConnector="├──"
    local panePrefix="│   "
    if [ "$isLast" -eq 1 ]; then
      sessConnector="└──"
      panePrefix="    "
    fi

    local sessLabel="$sessName"
    if [ "$sessAttached" -gt 0 ] 2>/dev/null; then
      sessLabel="$sessName (attached, $sessDate)"
    else
      sessLabel="$sessName ($sessDate)"
    fi

    echo "$sessConnector $sessLabel"

    local paneData
    paneData=$($TMUX_CMD list-panes -t "$sessName" -s \
      -F "#{window_index}|#{pane_index}|#{pane_title}|#{pane_current_command}" 2>/dev/null)

    local paneCount
    paneCount=$(echo "$paneData" | wc -l | tr -d ' ')
    local paneI=0

    echo "$paneData" | while IFS='|' read -r winIdx paneIdx paneTitle paneCmd; do
      paneI=$((paneI + 1))

      local address="${winIdx}.${paneIdx}"
      local title="${paneTitle:--}"

      local paneConnector="├──"
      [ "$paneI" -eq "$paneCount" ] && paneConnector="└──"

      # When paneCmd is bash/zsh, check for Claude child process and show version
      local displayCmd="$paneCmd"
      if [[ "$paneCmd" == "bash" || "$paneCmd" == "zsh" ]]; then
        local paneTargetVer="${sessName}:${winIdx}.${paneIdx}"
        if "$ooshDir/claudeCode" process.running "$paneTargetVer" 2>/dev/null; then
          local ver
          ver=$("$ooshDir/claudeCode" version "$paneTargetVer" 2>/dev/null)
          ver="${ver%% (*}"  # trim " (Claude Code)" suffix
          [ -n "$ver" ] && displayCmd="$ver"
        fi
      fi
      printf "%s%s %-5s %-24s [%s]\n" "$panePrefix" "$paneConnector" "$address" "$title" "$displayCmd"

      # Third level: only for panes running Claude Code (not plain shells)
      local paneTarget="${sessName}:${winIdx}.${paneIdx}"
      local agentName="" agentSid="" agentModel=""

      # Detect Claude process — must be running to get a sub-branch
      if "$ooshDir/claudeCode" process.find "$paneTarget" >/dev/null 2>&1; then
        agentSid=$("$ooshDir/claudeCode" session.id "$paneTarget" 2>/dev/null)

        # Get agent name from session (customTitle via /rename)
        if [ -n "$agentSid" ]; then
          agentName=$("$ooshDir/claudeCode" session.name "$agentSid" 2>/dev/null)
          # Reject firstPrompt garbage — only keep short role names (no spaces, < 40 chars)
          if [ -n "$agentName" ] && { [[ "$agentName" == *" "* ]] || [ ${#agentName} -gt 40 ]; }; then
            agentName=""
          fi
        fi

        # Fallback to hiveMind registry for role name
        if [ -z "$agentName" ]; then
          local reg="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"
          if [ -f "$reg" ]; then
            agentName=$(grep "^${paneTarget}|" "$reg" 2>/dev/null | cut -d'|' -f2)
          fi
        fi

        # Extract model from JSONL if agentName lacks @model
        if [ -n "$agentName" ] && [[ "$agentName" != *"@"* ]] && [ -n "$agentSid" ]; then
          local jsonlPath
          jsonlPath=$(find ~/.claude/projects -name "${agentSid}.jsonl" 2>/dev/null | head -1)
          if [ -n "$jsonlPath" ]; then
            agentModel=$(grep -o '"model":"[^"]*"' "$jsonlPath" 2>/dev/null | tail -1 | sed 's/"model":"//;s/"//')
            case "$agentModel" in
              *opus*)   agentModel="opus" ;;
              *sonnet*) agentModel="sonnet" ;;
              *haiku*)  agentModel="haiku" ;;
            esac
            [ -n "$agentModel" ] && agentName="${agentName}@${agentModel}"
          fi
        fi

        # Show sub-branch for Claude instances only
        if [ -n "$agentName" ] || [ -n "$agentSid" ]; then
          local subPrefix="│     "
          [ "$paneI" -eq "$paneCount" ] && subPrefix="      "
          local subName="${agentName:-(unnamed)}"
          if [ -n "$agentSid" ]; then
            printf "%s%s└ %-30s [%s]\n" "$panePrefix" "$subPrefix" "$subName" "$agentSid"
          else
            printf "%s%s└ %s\n" "$panePrefix" "$subPrefix" "$subName"
          fi
        fi
      fi
    done

    echo "${panePrefix}"
  done
}
otmux.status() # # show current tmux status (tree overview)
{
  otmux.tree
}

# ─────────────────────────────────────────────────────────────────────────────
# OBJECT.VERB ALIASES (tmux.object.verb style)
# ─────────────────────────────────────────────────────────────────────────────
# These provide an alternative naming convention: otmux.object.verb
# instead of otmux.verbObject, for those who prefer noun-first naming.

# --- SESSION ALIASES ---

otmux.session.new() # <?name> <?command> # create new session; name=session identifier
{
  otmux.new "$@"
}

otmux.session.attach() # <?target> # attach to session; target=session name or $id
{
  otmux.attach "$@"
}
otmux.session.attach.completion.target() { private.complete.sessions; }

otmux.session.detach() # # detach from current session
{
  otmux.detach "$@"
}

otmux.session.list() # # list all sessions
{
  otmux.sessions "$@"
}

otmux.session.has() # <target> # check if session exists; target=session name
{
  otmux.has "$@"
}
otmux.session.has.completion.target() { private.complete.sessions; }

otmux.session.kill() # <target> # kill session; target=session name to destroy
{
  otmux.kill "$@"
}
otmux.session.kill.completion.target() { private.complete.sessions; }

otmux.session.kill.all() # # kill all sessions except current
{
  private.otmux.killAll "$@"
}

otmux.session.rename() # <name> # rename current session; name=new session name
{
  otmux.rename "$@"
}

otmux.session.switch() # <target> # switch to session; target=session name or $id
{
  otmux.switch "$@"
}
otmux.session.switch.completion.target() { private.complete.sessions; }

otmux.session.last() # # switch to last active session
{
  otmux.last "$@"
}

otmux.session.next() # # switch to next session
{
  otmux.next "$@"
}

otmux.session.prev() # # switch to previous session
{
  otmux.prev "$@"
}

otmux.session.lock() # # lock current session
{
  otmux.lock "$@"
}

otmux.session.details() # <session> # show all windows and panes with titles, commands, and dimensions
{
  local session="$1"

  if [ -z "$session" ]; then
    error.log "usage: otmux session.details <session-name>"
    return 1
  fi

  if ! $TMUX_CMD has-session -t "$session" 2>/dev/null; then
    error.log "session not found: $session"
    return 1
  fi

  echo "Session: $session"
  echo "═══════════════════════════════════════════════════════════════════"

  local currentWindow=""

  $TMUX_CMD list-panes -t "$session" -s \
    -F "#{window_index}|#{window_name}|#{pane_index}|#{pane_title}|#{pane_current_command}|#{pane_width}|#{pane_height}|#{pane_active}" 2>/dev/null | \
    while IFS='|' read -r winIdx winName paneIdx paneTitle paneCmd paneW paneH paneActive; do
      if [ "$winIdx" != "$currentWindow" ]; then
        [ -n "$currentWindow" ] && echo ""
        echo "  Window $winIdx: $winName"
        echo "  ───────────────────────────────────────────────────────────────"
        printf "  %-30s %-16s %-12s %-10s %s\n" "Address" "Title" "Command" "Size" ""
        currentWindow="$winIdx"
      fi

      local address="${session}:${winIdx}.${paneIdx}"
      local size="${paneW}x${paneH}"
      local activeMark=""
      [ "$paneActive" = "1" ] && activeMark="(active)"

      printf "  %-30s %-16s %-12s %-10s %s\n" "$address" "${paneTitle:--}" "$paneCmd" "$size" "$activeMark"
    done

  echo ""
}
# --- WINDOW ALIASES ---

otmux.window.new() # <?name> <?command> # create window; name=window title
{
  private.otmux.newWindow "$@"
}

otmux.window.list() # # list windows in current session
{
  otmux.windows "$@"
}

otmux.window.get() # <?format:#> # get current window target; format=# for index, @ for id, name for name
{
  local format="${1:-#}"
  case "$format" in
    \#)   $TMUX_CMD display-message -p "#{window_index}" ;;
    @)    $TMUX_CMD display-message -p "#{window_id}" ;;
    name) $TMUX_CMD display-message -p "#{window_name}" ;;
    *)    $TMUX_CMD display-message -p "$format" ;;
  esac
}

otmux.window.get.target() # # get current window in target format (session:window)
{
  $TMUX_CMD display-message -p "#{session_name}:#{window_index}"
}

otmux.window.select() # <target> # select window; target=index (0,1,2) or name
{
  private.otmux.selectWindow "$@"
}
otmux.window.select.completion.target() { private.complete.windows; }

otmux.window.next() # # go to next window
{
  private.otmux.nextWindow "$@"
}

otmux.window.prev() # # go to previous window
{
  private.otmux.prevWindow "$@"
}

otmux.window.last() # # go to last active window
{
  private.otmux.lastWindow "$@"
}

otmux.window.kill() # <?target> # kill window; target=[session:]window index/name
{
  private.otmux.killWindow "$@"
}
otmux.window.kill.completion.target() { private.complete.windows; }

otmux.window.rename() # <target|name> <?name> # rename window; 2 args = target + name, 1 arg = name only
{
  private.otmux.renameWindow "$@"
}

otmux.window.move() # <target> # move window; target=destination [session:]index
{
  private.otmux.moveWindow "$@"
}
otmux.window.move.completion.target() { private.complete.windows; }

otmux.window.swap() # <target> # swap with window; target=[session:]window to swap with
{
  private.otmux.swapWindow "$@"
}
otmux.window.swap.completion.target() { private.complete.windows; }

otmux.window.link() # <src> <dst> # link window; src=source window, dst=target session
{
  private.otmux.linkWindow "$@"
}
otmux.window.link.completion.src() { private.complete.windows; }
otmux.window.link.completion.dst() { private.complete.sessions; }

otmux.window.unlink() # <?target> # unlink window; target=window to unlink
{
  private.otmux.unlinkWindow "$@"
}
otmux.window.unlink.completion.target() { private.complete.windows; }

otmux.window.find() # <pattern> # find window by pattern; pattern=search string
{
  private.otmux.findWindow "$@"
}

otmux.window.rotate() # # rotate panes within window
{
  private.otmux.rotateWindow "$@"
}

otmux.window.respawn() # <?command> # respawn window; command=shell command to run
{
  private.otmux.respawnWindow "$@"
}

# --- PANE ALIASES ---

otmux.pane.split() # <?command> # split pane horizontally; command=run in new pane
{
  otmux.split "$@"
}

otmux.pane.split.h() # <?command> # split horizontally (side-by-side)
{
  private.otmux.splitH "$@"
}

otmux.pane.split.v() # <?command> # split vertically (top-bottom)
{
  private.otmux.splitV "$@"
}

otmux.pane.list() # <?session> # list panes with addresses and titles; session=filter to session
{
  if [ -n "$1" ]; then
    $TMUX_CMD list-panes -t "$1" -s \
      -F "#{session_name}:#{window_index}.#{pane_index}  #{pane_title}  #{pane_current_command}" 2>/dev/null
  else
    otmux.panes "$@"
  fi
}


otmux.pane.get() # <?target> <?format:%> # get pane info; 2 args = target + format, 1 arg = format, 0 = pane_id
{
  local targetArgs=(-t "${TMUX_PANE:-}") format="%"
  if [ $# -ge 2 ]; then
    targetArgs=(-t "$1"); format="$2"
  elif [ $# -eq 1 ]; then
    format="$1"
  fi
  case "$format" in
    %)  $TMUX_CMD display-message "${targetArgs[@]}" -p "#{pane_id}" ;;
    \#) $TMUX_CMD display-message "${targetArgs[@]}" -p "#{pane_index}" ;;
    *)  $TMUX_CMD display-message "${targetArgs[@]}" -p "$format" ;;
  esac
}

otmux.pane.get.target() # # get executing pane in target format (session:window.pane)
{
  $TMUX_CMD display-message -t "${TMUX_PANE:-}" -p "#{session_name}:#{window_index}.#{pane_index}"
}

otmux.pane.select() # <target> # select pane; target=U/D/L/R direction or index (0,1,2)
{
  private.otmux.selectPane "$@"
}


otmux.pane.up() # # select pane above
{
  otmux.up "$@"
}

otmux.pane.down() # # select pane below
{
  otmux.down "$@"
}

otmux.pane.left() # # select pane to the left
{
  otmux.left "$@"
}

otmux.pane.right() # # select pane to the right
{
  otmux.right "$@"
}

otmux.pane.last() # # select last active pane
{
  private.otmux.lastPane "$@"
}

otmux.pane.kill() # <?target> # kill pane; target=[session:][window.]pane index
{
  private.otmux.killPane "$@"
}


otmux.pane.break() # # break pane into new window
{
  private.otmux.breakPane "$@"
}

otmux.pane.join() # <window> # join pane to window; window=destination [session:]window
{
  private.otmux.joinPane "$@"
}

otmux.pane.swap() # <sourcePane> <targetPane> # swap two panes; sourcePane/targetPane=session:window.pane
{
  local sourcePane=$(private.resolve.target "$1")
  local targetPane=$(private.resolve.target "$2")
  if [ -z "$sourcePane" ] || [ -z "$targetPane" ]; then
    error.log "usage: otmux pane.swap <sourcePane> <targetPane>"
    return 1
  fi
  $TMUX_CMD swap-pane -s "$sourcePane" -t "$targetPane"
}

otmux.pane.move() # <window> # move pane; window=destination window
{
  private.otmux.movePane "$@"
}

otmux.pane.resize() # <direction> <?amount> # resize pane; direction=U/D/L/R, amount=cells (default 5)
{
  private.otmux.resizePane "$@"
}


otmux.pane.zoom() # # toggle pane zoom (fullscreen)
{
  otmux.zoom "$@"
}

otmux.pane.respawn() # <?command> # respawn pane; command=shell command to run
{
  private.otmux.respawnPane "$@"
}

otmux.pane.pipe() # <?command> # pipe pane output; command=receive piped output
{
  private.otmux.pipePane "$@"
}

otmux.pane.display() # # display pane numbers temporarily
{
  private.otmux.displayPanes "$@"
}

otmux.pane.capture() # <target> <?lines:20> # capture last N lines of visible pane output; target=session:window.pane
{
  local target=$(private.resolve.target "$1")
  local lines="${2:-20}"
  if [ -z "$target" ]; then
    error.log "usage: otmux pane.capture <target> <?lines>"
    return 1
  fi
  $TMUX_CMD capture-pane -t "$target" -p -S "-${lines}" 2>/dev/null | tail -"$lines"
}


otmux.pane.capture.full() # <target> # capture full scrollback of a pane; target=session:window.pane
{
  local target=$(private.resolve.target "$1")
  if [ -z "$target" ]; then
    error.log "usage: otmux pane.capture.full <target>"
    return 1
  fi
  $TMUX_CMD capture-pane -t "$target" -p -S - 2>/dev/null
}
otmux.pane.capture.full.completion.target() { private.complete.panes; }

otmux.pane.capture.visible() # <target> # capture only the visible pane area (no scrollback)
{
  local target=$(private.resolve.target "$1")
  if [ -z "$target" ]; then
    error.log "usage: otmux pane.capture.visible <target>"
    return 1
  fi
  $TMUX_CMD capture-pane -t "$target" -p 2>/dev/null
}


otmux.pane.history() # <target> <?lines:100> # capture scrollback history including content scrolled off screen
{
  local target=$(private.resolve.target "$1")
  local lines="${2:-100}"
  if [ -z "$target" ]; then
    error.log "usage: otmux pane.history <target> <?lines>"
    return 1
  fi
  $TMUX_CMD capture-pane -t "$target" -p -S "-${lines}" 2>/dev/null
}


otmux.pane.history.clear() # # clear pane scrollback history
{
  private.otmux.clearHistory "$@"
}

otmux.pane.title() # <target> <title> # set pane title; target=session:window.pane
{
  local target=$(private.resolve.target "$1")
  local title="$2"
  if [ -z "$title" ]; then
    error.log "usage: otmux pane.title <target> <title>"
    return 1
  fi
  $TMUX_CMD select-pane -t "$target" -T "$title"
}


otmux.pane.send() # <target> <text> # send text + Enter to pane; target=session:window.pane
{
  private.otmux.sendEnter "$@"
}


otmux.pane.lock() # <target> <title> # lock pane title; prevents Claude Code from overwriting
{
  local target=$(private.resolve.target "$1")
  local title="$2"
  if [ -z "$target" ] || [ -z "$title" ]; then
    error.log "usage: otmux pane.lock <target> <title>"
    return 1
  fi
  # Remove old hook first (prevents old hook from reverting the new title)
  $TMUX_CMD set-hook -up -t "$target" pane-title-changed 2>/dev/null
  # Set the title
  $TMUX_CMD select-pane -t "$target" -T "$title"
  # Block escape-sequence-based renaming
  $TMUX_CMD set-option -p -t "$target" allow-rename off
  # Hook to re-enforce title on any change (catches tmux command-based renaming too)
  $TMUX_CMD set-hook -p -t "$target" pane-title-changed "select-pane -T \"$title\""
  info.log "Locked pane $target title to: $title"
}


otmux.pane.unlock() # <target> # unlock pane title; allows normal title changes
{
  local target=$(private.resolve.target "$1")
  if [ -z "$target" ]; then
    error.log "usage: otmux pane.unlock <target>"
    return 1
  fi
  # Remove the title enforcement hook
  $TMUX_CMD set-hook -up -t "$target" pane-title-changed
  # Re-enable escape-sequence renaming
  $TMUX_CMD set-option -up -t "$target" allow-rename
  info.log "Unlocked pane $target title"
}


# --- LAYOUT ALIASES ---

otmux.layout.set() # <layout> # set layout; layout=even-horizontal/vertical/main-horizontal/vertical/tiled
{
  otmux.layout "$@"
}


otmux.layout.even.h() # # set even-horizontal layout (equal width columns)
{
  private.otmux.evenH "$@"
}

otmux.layout.even.v() # # set even-vertical layout (equal height rows)
{
  private.otmux.evenV "$@"
}

otmux.layout.main.h() # # set main-horizontal layout (large pane on top)
{
  private.otmux.mainH "$@"
}

otmux.layout.main.v() # # set main-vertical layout (large pane on left)
{
  private.otmux.mainV "$@"
}

otmux.layout.tiled() # # set tiled layout (grid arrangement)
{
  otmux.tiled "$@"
}

otmux.layout.next() # # cycle to next layout
{
  private.otmux.nextLayout "$@"
}

otmux.layout.prev() # # cycle to previous layout
{
  private.otmux.prevLayout "$@"
}

# --- BUFFER ALIASES ---

otmux.buffer.list() # # list all buffers
{
  otmux.buffers "$@"
}

otmux.buffer.show() # <?buffer> # show buffer contents; buffer=buffer name
{
  private.otmux.showBuffer "$@"
}
otmux.buffer.set() # <data> # set buffer data; data=text to store
{
  private.otmux.setBuffer "$@"
}

otmux.buffer.load() # <file> # load buffer from file; file=path to read
{
  private.otmux.loadBuffer "$@"
}

otmux.buffer.save() # <file> # save buffer to file; file=path to write
{
  private.otmux.saveBuffer "$@"
}

otmux.buffer.delete() # <?buffer> # delete buffer; buffer=buffer name
{
  private.otmux.deleteBuffer "$@"
}
otmux.buffer.paste() # # paste buffer contents into pane
{
  private.otmux.pasteBuffer "$@"
}

otmux.buffer.choose() # # interactively choose buffer
{
  private.otmux.chooseBuffer "$@"
}

# --- CLIENT ALIASES ---

otmux.client.list() # # list connected clients
{
  otmux.clients "$@"
}

otmux.client.detach() # <?client> # detach client; client=client tty (e.g., /dev/pts/0)
{
  private.otmux.detachClient "$@"
}
otmux.client.suspend() # # suspend current client (Ctrl+Z)
{
  private.otmux.suspendClient "$@"
}

otmux.client.refresh() # # refresh client display
{
  private.otmux.refreshClient "$@"
}

otmux.client.choose() # # interactively choose client
{
  private.otmux.chooseClient "$@"
}

otmux.client.choose.tree() # # interactively choose session/window tree
{
  private.otmux.chooseTree "$@"
}

otmux.client.lock() # # lock current client
{
  private.otmux.lockClient "$@"
}

# --- SERVER ALIASES ---

otmux.server.start() # # start tmux server
{
  private.otmux.startServer "$@"
}

otmux.server.kill() # # kill tmux server and all sessions
{
  private.otmux.killServer "$@"
}

otmux.server.lock() # # lock all clients
{
  private.otmux.lockServer "$@"
}

otmux.server.info() # # show server information
{
  otmux.info "$@"
}

# --- CONFIG ALIASES ---

otmux.config.source() # <?file> # source config file; file=path (default ~/.tmux.conf)
{
  otmux.source "$@"
}

otmux.config.set() # <option> <value> # set option; option=tmux option name
{
  otmux.set "$@"
}

otmux.config.set.global() # <option> <value> # set global option
{
  private.otmux.setGlobal "$@"
}

otmux.config.set.window() # <option> <value> # set window option
{
  private.otmux.setWindow "$@"
}

otmux.config.show() # <?option> # show options; option=specific option or all
{
  otmux.show "$@"
}

otmux.config.show.global() # <?option> # show global options
{
  private.otmux.showGlobal "$@"
}

otmux.config.show.window() # <?option> # show window options
{
  private.otmux.showWindow "$@"
}

otmux.config.show.hooks() # # show configured hooks
{
  private.otmux.showHooks "$@"
}

otmux.config.set.hook() # <hook> <command> # set hook; hook=event name, command=action
{
  private.otmux.setHook "$@"
}

# --- KEY ALIASES ---

otmux.key.bind() # <key> <command> # bind key; key=key sequence, command=tmux command
{
  otmux.bind "$@"
}

otmux.key.unbind() # <key> # unbind key; key=key sequence to remove
{
  otmux.unbind "$@"
}

otmux.key.list() # # list all key bindings
{
  otmux.keys "$@"
}

# --- ENV ALIASES ---

otmux.env.set() # <name> <value> # set environment variable; name=var name
{
  private.otmux.setEnv "$@"
}

otmux.env.show() # <?name> # show environment; name=specific var or all
{
  private.otmux.showEnv "$@"
}

# --- SEND ALIASES ---

otmux.send.keys() # <target> <keys> # send keys to pane; target=pane, keys=sequence (e.g., "ls -la" Enter)
{
  otmux.send "$@"
}

otmux.send.enter() # <target> <text> # send text + Enter; target=pane, text=command to execute
{
  private.otmux.sendEnter "$@"
}

otmux.send.tui() # <target> <key1> [<key2>...] # send keys with inter-key delays for TUI apps (Claude Code)
{
  private.otmux.sendKeys "$@"
}


otmux.send.run() # <command> # run shell command in background
{
  otmux.run "$@"
}

otmux.send.display() # <message> # display message in status line
{
  otmux.display "$@"
}

otmux.send.confirm() # <command> # confirm before running; command=requires y/n
{
  otmux.confirm "$@"
}

otmux.send.prompt() # <?template> # show command prompt; template=prompt format
{
  otmux.prompt "$@"
}

otmux.send.menu() # # display interactive menu
{
  otmux.menu "$@"
}

# --- COPY ALIASES ---

otmux.copy.mode() # # enter copy mode for scrollback/selection
{
  otmux.copy "$@"
}

otmux.copy.clock() # # show clock in current pane
{
  otmux.clock "$@"
}

# ─────────────────────────────────────────────────────────────────────────────
# USAGE
# ─────────────────────────────────────────────────────────────────────────────

otmux.usage()
{
  local this=${0##*/}
  echo "You started"
  echo "$0

  otmux - tmux wrapper for oosh
  Makes tmux commands easy to remember!

  Two naming styles available:
    verbObject:    otmux killWindow dev:0
    object.verb:   otmux window.kill dev:0

  Usage:
  $this command     Description
  ─────────────────────────────────────────────────────
  SESSIONS:
      new <name>      create new session
      attach <sess>   attach to session (a)
      detach          detach from session (d)
      sessions        list sessions (ls)
      kill <sess>     kill a session
      rename <name>   rename session
      switch <sess>   switch to session
      last/next/prev  navigate sessions

  WINDOWS:
      newWindow <n>   create window (nw)
      windows         list windows (lsw)
      selectWindow    select window (sw)
      killWindow      kill window (kw)
      renameWindow    rename window (rw)
      nextWindow      next window
      prevWindow      previous window
      findWindow      find by pattern

  PANES:
      split           split horizontally
      splitH/splitV   split h/v explicitly
      panes           list panes (lsp)
      selectPane      select pane (sp)
      up/down/l/r     navigate panes
      killPane        kill pane (kp)
      zoom            toggle zoom (z)
      resizePane      resize pane (rp)
      breakPane       break to window
      joinPane        join to window
      displayPanes    show pane numbers (dp)

  LAYOUTS:
      layout <type>   set layout
      evenH/evenV     even layouts
      mainH/mainV     main layouts
      tiled           tiled layout
      nextLayout      cycle layouts

  BUFFERS:
      buffers         list buffers (lsb)
      paste           paste buffer
      chooseBuffer    choose buffer
      saveBuffer      save to file
      loadBuffer      load from file

  CONFIG:
      source <file>   source config
      set/setGlobal   set options
      show/showGlobal show options
      bind/unbind     key bindings
      keys            list bindings (lsk)

  UTILITY:
      send <keys>     send keys to pane
      run <cmd>       run shell command
      display <msg>   show message
      copy            enter copy mode
      clock           show clock
      status          show status
      v               show version
  ─────────────────────────────────────────────────────

  OBJECT.VERB STYLE (alternative):
      session.new/list/details/kill/switch/attach
      window.new/list/kill/select/move/swap
      pane.split/list/kill/select/resize/zoom/lock/unlock
      layout.set/evenH/evenV/mainH/mainV/tiled
      buffer.list/show/set/paste/delete
      client.list/detach/suspend/lock
      server.start/kill/info
      config.source/set/show
      key.bind/unbind/list
      send.keys/enter/run/display
  ─────────────────────────────────────────────────────

  Target formats:
    Session:  name or \$id (e.g., 'dev', '\$1')
    Window:   [session:]index/name (e.g., 'dev:0', '2')
    Pane:     [session:][window.]index (e.g., 'dev:0.1')

  Examples:
    $this new dev                 # new session 'dev'
    $this splitH                  # split horizontally
    $this send 'ls -la' Enter     # send command
    $this zoom                    # toggle pane zoom
    $this layout tiled            # tiled layout
    $this attach dev              # attach to 'dev'

    # object.verb style:
    $this session.new dev         # same as above
    $this pane.splitH             # split horizontally
    $this pane.zoom               # toggle zoom
    $this window.kill dev:0       # kill window 0 in dev
  "
}

otmux.start()
{
  #echo "sourcing init"
  source this

  # Auto-install tmux if missing (skip for completion discovery)
  if [[ "$1" != completion.discover ]] && ! command -v tmux &>/dev/null; then
    otmux.install || return 1
  fi

  if [ -z "$1" ]; then
    otmux.status
    return 0
  fi

  this.start "$@"
}

# Only auto-start when otmux is INVOKED as a script — not when sourced.
# Sourcing otmux (e.g. log.live.panes.stop in log:498 sources it for the
# function definitions) used to fire otmux.start unconditionally, which
# triggers `otmux.install` (oo cmd tmux → $SUDO $OOSH_PM tmux) when
# tmux is missing. On Debian's `os platform.test debian_12` flow, that
# blocks waiting for bash-user's sudo password mid-test (test.log T37
# sources otmux, hits the install, hangs on `[sudo] password for
# bash-user:`). Mirrors the `if (this.isSourced)` guard that other oosh
# scripts use (e.g. this:1027).
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
  otmux.start "$@"
fi
