#compdef lttng
#
# SPDX-FileCopyrightText: 2015-2025 Philippe Proulx <eeppeliteloop@gmail.com>
# SPDX-License-Identifier: MIT
#
# This is a Zsh completion function for the lttng(1) command (see
# <https://lttng.org/>), for versions 2.5 to 2.15.
#
# If you want, at your own risk, the function to work with versions
# above 2.15, set `LTTNG_ZSH_COMP_IGNORE_VERSION_LIMIT=1`.
#
# Each subcommand changes the command part of the current context so
# that you can target a specific subcommand with `zstyle`, for
# example:
#
#     $ zstyle ':completion:*:*:lttng-start:*:sessions' verbose yes
#     $ zstyle ':completion:*:*:lttng-add-context:type-option:*' \
#              group-order perf-context-types static-context-types
#
# The relevant style arguments and tags are:
#
# `actions` tag (with the `lttng-snapshot` command):
#     Snapshot action names.
#
# `channel` argument and `channels` tag:
#     Channel names.
#
#     The `verbose` style controls whether or not this function adds
#     completions as a list (with channel information for each item) or
#     simple channel names.
#
#     If the verbose style is enabled, then the information part of each
#     item starts with either `  [enabled` or `  [disabled` (note the
#     two leading spaces), depending on the status of the channel.
#
# `commands` tag:
#     lttng(1) subcommand names.
#
# `log-levels` tag:
#     Log level names.
#
# `session` argument and `sessions` tag:
#     Recording session names.
#
#     The `verbose` style controls whether or not this function adds
#     completions as a list (with recording session information for each
#     item) or simple recording session names.
#
#     If the verbose style is enabled, then the information part of each
#     item starts with either `  [active` or `  [inactive` (note the two
#     leading spaces), depending on the status of the recording session.
#
# `type-option` argument:
#     `static-context-types` tag:
#         Statically-known context field types.
#
#     `perf-context-types` tag:
#         perf counter context field types.
#
# `triggers` tag:
#     Trigger names.

# Sets the `minor_version` parameter to the minor version of
# LTTng-tools, or to `0` if not found.
__lttng_set_minor_version() {
  minor_version=0

  local -a match

  if [[ $($prog_name --version) =~ '[[:blank:]]+2\.([[:digit:]]+)' ]]; then
    minor_version=$match[1]
  fi
}

# Usage:
#
#     __lttng_run [ARG]...
#
# Runs the `lttng` command with the given ARG arguments
# using `tracing_group`.
__lttng_run() {
  $prog_name --no-sessiond --group=$tracing_group "$@" 2>/dev/null
}

# Usage:
#
#     __lttng_mi_run MSG [ARG]...
#
# Runs the `__lttng_run --mi=xml` command with the given ARG values, and
# sets `REPLY` to the XML output.
#
# On failure, show MSG.
#
# This function returns 1 with LTTng-tools < 2.6 and if `xmllint` isn't
# available. In this case, this function adds completion through
# _message(). Because of this, do NOT call this function in a subshell.
__lttng_mi_run() {
  local -r msg=$1

  shift

  if ((minor_version < 6)); then
    # MI output requires LTTng-tools ≥ 2.6
    _message -e descriptions $msg
    return 1
  fi

  if ((! $+commands[xmllint])); then
    # XML output parsing requires xmllint
    _message -e descriptions $msg
    return 1
  fi

  if ! REPLY=$(__lttng_run --mi=xml "$@"); then
    # Command failed
    _message -e descriptions $msg
    return 1
  fi
}

# Usage:
#
#     __lttng_style_is_verbose ARG TAG
#
# Returns whether or not the verbose style is enabled for the given
# argument ARG and tag TAG, using `curcontext` for the other parts of
# the full completion context.
__lttng_style_is_verbose() {
  local -r arg=$1 tag=$2
  local -r ccparts=("${(@s.:.)curcontext}")

  zstyle -t :completion:${ccparts[1]}:${ccparts[2]}:${ccparts[3]}:$arg:$tag verbose
}

# Returns 0 if a Linux kernel tracing domain option is set in
# `opt_args`, or 1 otherwise.
__lttng_kernel_domain_opt_is_set() {
  (($+opt_args[-k] || $+opt_args[--kernel] ||
    $+opt_args[domain--k] || $+opt_args[domain---kernel]))
}

# Returns 0 if a user space tracing domain option is set in
# `opt_args`, or 1 otherwise.
__lttng_user_domain_opt_is_set() {
  (($+opt_args[-u] || $+opt_args[--userspace] ||
    $+opt_args[domain--u] || $+opt_args[domain---userspace]))
}

# Returns 0 if a `java.util.logging` tracing domain option is set in
# `opt_args`, or 1 otherwise.
__lttng_jul_domain_opt_is_set() {
  (($+opt_args[-j] || $+opt_args[--jul] ||
    $+opt_args[domain--j] || $+opt_args[domain---jul]))
}

# Returns 0 if an Apache log4j 1.x tracing domain option is set in
# `opt_args`, or 1 otherwise.
__lttng_log4j1_domain_opt_is_set() {
  (($+opt_args[-l] || $+opt_args[--log4j] ||
    $+opt_args[domain--l] || $+opt_args[domain---log4j]))
}

# Returns 0 if an Apache Log4j 2 tracing domain option is set in
# `opt_args`, or 1 otherwise.
__lttng_log4j2_domain_opt_is_set() {
  (($+opt_args[--log4j2] || $+opt_args[domain---log4j2]))
}

# Returns 0 if a Python tracing domain option is set in `opt_args`,
# or 1 otherwise.
__lttng_python_domain_opt_is_set() {
  (($+opt_args[-p] || $+opt_args[--python] ||
    $+opt_args[domain--p] || $+opt_args[domain---python]))
}

# Usage:
#
#     __lttng_exclude_opts OPT...
#
# Excludes one or more options OPT from the possible options.
#
# Each OPT must match `--*` (long) or `-*` (short).
#
# This function removes description lines from the `long_opts` and
# `short_opts` arrays.
__lttng_exclude_opts() {
  local opt

  for opt in "$@"; do
    if [[ $opt == --* ]]; then
      long_opts=("${(@)long_opts:#$opt:*}")
    elif [[ $opt == -* ]]; then
      short_opts=("${(@)short_opts:#$opt:*}")
    fi
  done
}

# Usage:
#
#     __lttng_add_opts LONGOPT [SHORTOPT] DESCR
#
# Adds an option LONGOPT to `long_opts` and optionally short option
# SHORTOPT to `short_opts` with the description DESCR.
__lttng_add_opts() {
  local -r long_opt=$1
  local short_opt descr

  if [[ $2 == -* ]]; then
    short_opt=$2
    descr=$3
  else
    descr=$2
  fi

  long_opts+=("$long_opt:$descr")

  if [[ -n $short_opt ]]; then
    short_opts+=("$short_opt:$descr")
  fi
}

# Adds completions for the name of an `lttng` command.
__lttng_complete_cmd_name() {
  # LTTng-tools 2.5+
  local cmd_names=(
    'add-context:add context fields to be recorded'
    'create:create a recording session'
    'destroy:destroy a recording session'
    'disable-channel:disable channels'
    'disable-event:disable recording event rules'
    'enable-channel:create or enable a channel'
    'enable-event:create or enable recording event rules'
    'help:show the help of a command'
    'list:list recording sessions and instrumentation points'
    'set-session:set the current recording session'
    'snapshot:take a recording session snapshot'
    'start:start a recording session'
    'stop:stop a recording session'
    'version:show LTTng-tools version information'
    'view:launch a trace reader'
  )

  # LTTng-tools 2.6+
  if ((minor_version >= 6)); then
    cmd_names+=(
      'load:load recording session configurations'
      'save:save recording session configurations'
    )
  fi

  # LTTng-tools 2.7+
  if ((minor_version >= 7)); then
    cmd_names+=(
      'track:allow specific processes to record events'
      'untrack:disallow specific processes to record events'
    )
  fi

  # LTTng-tools 2.8+
  if ((minor_version >= 8)); then
    cmd_names+=(
      'status:show the status of the current recording session'
    )
  fi

  # LTTng-tools 2.9+
  if ((minor_version >= 9)); then
    cmd_names+=(
      'regenerate:regenerate specific recording session data'
    )
  fi

  # LTTng-tools 2.11+
  if ((minor_version >= 11)); then
    cmd_names+=(
      'rotate:archive the current trace chunk of a recording session'
      'enable-rotation:set a recording session rotation schedule'
      'disable-rotation:unset a recording session rotation schedule'
    )
  fi

  # LTTng-tools 2.12+
  if ((minor_version >= 12)); then
    cmd_names+=(
      'clear:clear a recording session'
    )
  fi

  # LTTng-tools 2.13+
  if ((minor_version >= 13)); then
    cmd_names+=(
      'add-trigger:add a trigger'
      'list-triggers:list triggers'
      'remove-trigger:remove a trigger'
    )
  fi

  # LTTng-tools 2.15+
  if ((minor_version >= 15)); then
    cmd_names+=(
      'reclaim-memory:reclaim channel memory immediately'
    )
  fi

  # Add completions
  _describe -t commands command cmd_names
}

# Adds completions for the name of an `lttng snapshot` action.
__lttng_complete_snapshot_action_name() {
  local -r action_names=(
    'add-output:add a snapshot output'
    'del-output:remove a snapshot output'
    'list-output:show the snapshot output'
    'record:take a snapshot'
  )

  # Add completions
  _describe -t actions 'snapshot action' action_names
}

# Adds user ID completions.
#
# The added completions are displayed as user names, but the actual
# completions are corresponding user IDs.
__lttng_complete_uid() {
  if [[ ! -f /etc/passwd ]]; then
    # Weird flex but ok
    _message -e descriptions 'user ID'
    return
  fi

  # Read user names and IDs from `/etc/passwd`
  local line
  local -a uids names fields

  while read -r line; do
    if [[ -z ${line// } ]]; then
      # Skip empty line
      continue
    fi

    # Extract user ID and name
    fields=("${(@s/:/)line}")
    uids+=($fields[3])
    names+=("$fields[1]")
  done < /etc/passwd

  # Add completions
  expl=
  _wanted users expl 'user ID' compadd -d names -a uids
}

# Adds completions for a context field type (for the `--type` option of
# the `add-context` command).
#
# This function replaces the argument field of the current context with
# `type-option`.
#
# This function relies on the tracing domain options of `opt_args` to
# restrict the offered completions. Without a tracing domain option,
# this function adds all the possible context field type completions.
#
# This function runs `lttng add-context --list` to get the list of
# available context field types.
#
# This function adds completions with both the `static-context-types`
# and `perf-context-types` tags so that the Zsh user can hide a group or
# select them with the `group-order` and `tag-order` style.
__lttng_complete_context_type() {
  # Statically-known context field types
  local kernel_types=(
    pid procname prio nice vpid tid vtid ppid vppid hostname
    interruptible preemptible need_reschedule migratable
    callstack-kernel callstack-user cgroup_ns ipc_ns mnt_ns net_ns
    pid_ns time_ns user_ns uts_ns uid euid suid gid egid sgid vuid veuid
    vsuid vgid vegid vsgid
  )
  local user_types=(
    vpid pthread_id procname ip cgroup_ns ipc_ns mnt_ns net_ns pid_ns
    time_ns user_ns uts_ns vuid veuid vsuid vgid vegid vsgid
  )

  # `cpu_id` user context type requires LTTng-tools ≥ 2.14
  if ((minor_version >= 14)); then
    user_types+=(cpu_id)
  fi

  # Set `types` and `domain` depending on the selected tracing domain
  local -a types
  local domain descr_suffix

  if __lttng_kernel_domain_opt_is_set; then
    # Kernel context field types
    types=($kernel_types)
    domain=k
  elif __lttng_user_domain_opt_is_set; then
    # User space context field types
    types=($user_types)
    domain=u
  else
    # No supported tracing domain: offer all context field types
    types=($user_types $kernel_types)
    descr_suffix=' (all tracing domains)'
  fi

  # Get XML list of available context field types
  local -r msg='context type'

  if ! __lttng_mi_run $msg add-context --list; then
    return 0
  fi

  local list_xml=$REPLY

  # Convert to one context field type per line
  local lines

  if ! lines=$(__lttng_xmllint_xpath $list_xml '//*[local-name()="symbol"]/text()'); then
    _guard '^-*' $msg
    return
  fi

  # Filter context field types depending on the selected tracing domain
  local -a static_items perf_items
  local line

  while read -r line; do
    if (($types[(Ie)$line])); then
      # Statically-known context field type
      static_items+=$line
    elif [[ $line == perf:cpu:* && $domain != u ]]; then
      # Per-CPU perf counter
      perf_items+=$line
    elif [[ $line == perf:thread:* && $domain != k ]]; then
      # Per-thread perf counter
      perf_items+=$line
    elif [[ $line == perf:* && $domain != u ]]; then
      # Other perf counter (Linux kernel tracing domain or none)
      perf_items+=$line
    fi
  done <<< $lines

  # Add completions
  _alternative -C type-option \
    "static-context-types:statically-known context type${descr_suffix}:compadd -a static_items" \
    "perf-context-types:perf counter context type${descr_suffix}:compadd -a perf_items"
}

# Adds completions for a trigger name.
#
# Relies on `opt_args[--owner-uid]` to restrict the offered
# completions.
__lttng_complete_trigger_name() {
  # Get XML list of available triggers
  local msg='trigger name'

  if ! __lttng_mi_run $msg list-triggers; then
    return 0
  fi

  local list_xml=$REPLY

  # Create the owner UID predicate XPath part
  local owner_uid_xpath

  if (($+opt_args[--owner-uid])); then
    owner_uid_xpath="[./*[local-name()='owner_uid'] = ${opt_args[--owner-uid]}]"
  fi

  # Convert to one trigger name per line
  local lines

  if ! lines=$(__lttng_xmllint_xpath $list_xml "//*[local-name()='trigger']$owner_uid_xpath/*[local-name()='name']/text()"); then
    _guard '^-*' $msg
    return
  fi

  local line
  local -a trigger_names

  while read -r line; do
    trigger_names+=$line
  done <<< $lines

  # Add completions
  expl=
  _wanted triggers expl $msg compadd -a trigger_names
}

# Usage:
#
#     __lttng_xmllint_xpath XMLDOC XPATH
#
# Runs `xmllint` with XML document XMLDOC and XPath XPATH
# to extract data.
__lttng_xmllint_xpath() {
  local -r xml=$1 xpath=$2

  xmllint --xpath $xpath - <<< $xml 2>/dev/null
}

# Usage:
#
#     __lttng_session_status_from_enabled_prop PROP
#
# Sets `REPLY` to the equivalent recording session status adjective
# (`active` or `inactive`) for an `enabled` XML property PROP, or
# `unknown` on error.
__lttng_session_status_from_enabled_prop() {
  local -r prop=$1

  case $prop in
    true) REPLY=active;;
    false) REPLY=inactive;;
    *) REPLY=unknown;;
  esac
}

# Usage:
#
#     __lttng_immediate_prop_from_xml NODEXML NODENAME PROPNAME
#
# Prints the value of the immediate XML property named PROPNAME of the
# child node named NODENAME within the parent node NODEXML.
__lttng_immediate_prop_from_xml() {
  local -r node_xml=$1 node_name=$2 prop_name=$3

  __lttng_xmllint_xpath $node_xml "/*[local-name()='$node_name']/*[local-name()='$prop_name']/text()"
}

# Usage:
#
#     __lttng_name_prop_from_xml NODEXML NODENAME
#
# Prints the value of the immediate XML `name` property of the child
# node named NODENAME within the parent node NODEXML.
__lttng_name_prop_from_xml() {
  local -r node_xml=$1 node_name=$2

  __lttng_immediate_prop_from_xml $node_xml $node_name name
}

# Usage:
#
#     __lttng_enabled_prop_from_xml NODEXML NODENAME
#
# Prints the value of the immediate XML `enabled` property of the child
# node named NODENAME within the parent node NODEXML.
__lttng_enabled_prop_from_xml() {
  local -r node_xml=$1 node_name=$2

  __lttng_immediate_prop_from_xml $node_xml $node_name enabled
}

# Usage:
#
#     __lttng_complete_session_name REQSTATUS REQMODE
#
# Adds completions for a recording session name.
#
# REQSTATUS is one of:
#
# `all`:
#     Add completions for active and inactive recording sessions.
#
# `active`:
#     Only add completions for active recording sessions.
#
# `inactive`:
#     Only add completions for inactive recording sessions.
#
# REQMODE is one of:
#
# `all`:
#     Add completions for recording sessions regardless of their mode.
#
# `snapshot`:
#     Only add completions for snapshot recording sessions.
#
# `live`:
#     Only add completions for LTTng live recording sessions.
__lttng_complete_session_name() {
  local -r req_status=$1 req_mode=$2

  # Get XML document (summary of recording sessions)
  if ! __lttng_mi_run 'recording session name' list; then
    return 0
  fi

  local session_summaries_xml=$REPLY

  # Count recording sessions
  local -i session_count

  session_count=$(__lttng_xmllint_xpath $session_summaries_xml 'count(//*[local-name()="session"])')

  if (($? != 0 || session_count == 0)); then
    _guard '^-*' 'recording session name'
    return
  fi

  # Append the name and info of one recording session at a time
  local -a session_names session_infos
  local -i index iprop
  local session_name session_summary_xml session_mode session_status session_info

  # For each recording session summary
  for index in {1..$session_count}; do
    # Get recording session summary XML node
    if ! session_summary_xml=$(__lttng_xmllint_xpath $session_summaries_xml "//*[local-name()='session'][$index]"); then
      continue
    fi

    # Get recording session name
    if ! session_name=$(__lttng_name_prop_from_xml $session_summary_xml session); then
      continue
    fi

    # Get recording session status
    if ! session_status=$(__lttng_enabled_prop_from_xml $session_summary_xml session); then
      continue
    fi

    __lttng_session_status_from_enabled_prop $session_status
    session_status=$REPLY

    if [[ $req_status != all && $req_status != $session_status ]]; then
      # Skip recording session with unexpected status
      continue
    fi

    # Get recording session mode
    session_mode=
    iprop=$(__lttng_immediate_prop_from_xml $session_summary_xml session snapshot_mode)

    if (($? == 0 && iprop)); then
      session_mode=snapshot
    else
      iprop=$(__lttng_immediate_prop_from_xml $session_summary_xml session live_timer_interval)

      if (($? == 0 && iprop)); then
        session_mode=live
      fi
    fi

    if [[ $req_mode != all && $req_mode != $session_mode ]]; then
      # Skip recording session with unexpected mode
      continue
    fi

    session_info=$session_status

    if [[ -n $session_mode ]]; then
      session_info+=", $session_mode mode"
    fi

    session_names+=("$session_name")
    session_infos+=($session_info)
  done

  # No recording sessions: show message
  if (($#session_names == 0)); then
    _guard '^-*' 'recording session name'
    return
  fi

  # Compute maximum session info length
  local -i max_info_len=0 len

  for session_info in $session_infos; do
    len=$#session_info

    if ((len > max_info_len)); then
      max_info_len=$len
    fi
  done

  # Compute maximum session name length
  local -i max_name_len=0

  for session_name in $session_names; do
    len=$#session_name

    if ((len > max_name_len)); then
      max_name_len=$len
    fi
  done

  # Some room for the longest info string, two spaces, and two brackets
  local -ir max_possible_name_len=$((COLUMNS - max_info_len - 5))

  if ((max_name_len > max_possible_name_len)); then
    # Clamp
    max_name_len=$max_possible_name_len
  fi

  # Create the dislay strings (name, status, mode)
  local -a disps

  for index in {1..${#session_names}}; do
    disps+=("${(r:$max_name_len:: :)session_names[$index]}  [$session_infos[$index]]")
  done

  # Add completions
  expl=

  if __lttng_style_is_verbose session sessions; then
    # Verbose mode (list with infos)
    _wanted -C session sessions expl 'recording session name' \
      compadd -d disps -l -a session_names
  else
    # Just the recording session names
    _wanted -C session sessions expl 'recording session name' \
      compadd -a session_names
  fi
}

# Sets `REPLY` to the name of the Unix user's current recording session,
# if any.
__lttng_cur_session_name() {
  local -r lttngrc_path=${LTTNG_HOME:-$HOME}/.lttngrc

  if [[ ! -f $lttngrc_path ]]; then
    return 1
  fi

  local line cur_session_name
  local -a match

  while read -r line || [[ -n $line ]]; do
    if [[ $line =~ 'session=([^[:blank:]]+)' ]]; then
      cur_session_name=$match[1]
      break
    fi
  done < $lttngrc_path

  if [[ -z $cur_session_name ]]; then
    return 1
  fi

  REPLY=$cur_session_name
}

# Usage:
#
#     __lttng_status_from_enabled_prop PROP
#
# Sets `REPLY` to the equivalent status adjective (`enabled` or
# `disabled`) for an `enabled` XML property PROP, or `unknown` on error.
__lttng_status_from_enabled_prop() {
  local -r prop=$1

  case $prop in
    true) REPLY=enabled;;
    false) REPLY=disabled;;
    *) REPLY=unknown;;
  esac
}

# Usage:
#
#     __lttng_session_xml MSG SESSIONNAME
#
# Sets `REPLY` to the XML document of `lttng list SESSIONNAME`
# (specifics of a single recording session).
#
# On error, show MSG.
#
# This function calls __lttng_mi_run(); see notes about
# __lttng_mi_run().
__lttng_session_xml() {
  local -r msg=$1 session_name=$2

  __lttng_mi_run $msg list $session_name
}

# Usage:
#
#     __lttng_complete_channel_name REQSTATUS SRC
#
# Adds completions for a channel name.
#
# This function relies on the tracing domain options of `opt_args`,
# `opt_args[-s]`, `opt_args[--session]`, and the first non-option
# argument to restrict the offered completions.
#
# REQSTATUS is one of:
#
# `all`:
#     Add completions for enabled and disabled channels.
#
# `enabled`:
#     Only add completions for enabled channels.
#
# `disabled`:
#     Only add completions for disabled channels.
#
# SRC is one of:
#
# `opt`:
#     Use `opt_args[-s]` and `opt_args[--session]` to find the name of
#     the selected recording session.
#
# `arg`:
#     Use `line[1]` to find the name of the selected recording session.
#
# Anything else:
#     Use the current recording session name.
__lttng_complete_channel_name() {
  local -r req_status=$1 session_name_src=$2

  shift 2

  # Find the recording session name to use
  local session_name

  if [[ $session_name_src == opt ]]; then
    if (($+opt_args[-s])); then
      session_name=$opt_args[-s]
    elif (($+opt_args[--session])); then
      session_name=$opt_args[--session]
    fi
  elif [[ $session_name_src == arg ]]; then
    session_name=$line[1]
  fi

  if [[ -z $session_name ]]; then
    # Fall back to current recording session
    if ! __lttng_cur_session_name; then
      _guard '^-*' 'channel name'
      return
    fi

    session_name=$REPLY
  fi

  # Get XML document (detailed recording session)
  if ! __lttng_session_xml 'channel name' $session_name; then
    return 0
  fi

  local session_xml=$REPLY

  # Count tracing domains
  local -i domain_count

  domain_count=$(__lttng_xmllint_xpath $session_xml 'count(//*[local-name()="domain"])')

  if (($? != 0 || domain_count == 0)); then
    _guard '^-*' 'channel name'
    return
  fi

  # Append the name and info of one channel at a time
  local domain_xml domain prop
  local channel_xml channel_name channel_status channel_erl_mode
  local -a channel_names channel_infos
  local -i channel_count domain_index channel_index

  # For each tracing domain
  for domain_index in {1..$domain_count}; do
    # Get tracing domain XML node
    if ! domain_xml=$(__lttng_xmllint_xpath $session_xml "//*[local-name()='domain'][$domain_index]"); then
      continue
    fi

    # Get tracing domain type
    if ! domain=$(__lttng_xmllint_xpath $domain_xml '/*/*[local-name()="type"]/text()'); then
      continue
    fi

    # Skip unexpected tracing domains
    if [[ $domain == KERNEL ]]; then
      if __lttng_user_domain_opt_is_set; then
        # Skip unexpected Linux kernel tracing domain
        continue
      fi

      domain='Linux kernel'
    fi

    if [[ $domain == UST ]]; then
      if __lttng_kernel_domain_opt_is_set; then
        # Skip unexpected user space tracing domain
        continue
      fi

      domain='user space'
    fi

    # Count channels within tracing domain
    channel_count=$(__lttng_xmllint_xpath $domain_xml 'count(//*[local-name()="channel"])')

    if (($? != 0 || channel_count == 0)); then
      continue
    fi

    # For each channel
    for channel_index in {1..$channel_count}; do
      # Get channel XML node
      if ! channel_xml=$(__lttng_xmllint_xpath $domain_xml "//*[local-name()='channel'][$channel_index]"); then
        continue
      fi

      # Get channel name
      if ! channel_name=$(__lttng_name_prop_from_xml $channel_xml channel); then
        continue
      fi

      # Get channel status
      if ! channel_status=$(__lttng_enabled_prop_from_xml $channel_xml channel); then
        continue
      fi

      __lttng_status_from_enabled_prop $channel_status
      channel_status=$REPLY

      if [[ $req_status != all && $req_status != $channel_status ]]; then
        # Skip channel with unexpected status
        continue
      fi

      # Get channel event record loss mode
      if ! channel_erl_mode=$(__lttng_xmllint_xpath $channel_xml '//*[local-name()="attributes"]/*[local-name()="overwrite_mode"]/text()'); then
        continue
      fi

      if [[ $channel_erl_mode == DISCARD ]]; then
        channel_erl_mode=discard
      elif [[ $channel_erl_mode == OVERWRITE ]]; then
        channel_erl_mode=overwrite
      fi

      channel_names+=("$channel_name")
      channel_infos+=("$channel_status, $domain, $channel_erl_mode mode")
    done
  done

  # No channels: show message
  if (($#channel_names == 0)); then
    _guard '^-*' 'channel name'
    return
  fi

  # Compute maximum channel info length
  local channel_info
  local -i max_info_len=0 len

  for channel_info in $channel_infos; do
    len=$#channel_info

    if ((len > max_info_len)); then
      max_info_len=$len
    fi
  done

  # Compute maximum channel name length
  local -i max_name_len=0

  for channel_name in $channel_names; do
    len=$#channel_name

    if ((len > max_name_len)); then
      max_name_len=$len
    fi
  done

  # Some room for the longest info string, two spaces, and two brackets
  local -ir max_possible_name_len=$((COLUMNS - max_info_len - 5))

  if ((max_name_len > max_possible_name_len)); then
    # Clamp
    max_name_len=$max_possible_name_len
  fi

  # Create the dislay strings (name, status, tracing domain, mode)
  local -a disps

  for channel_index in {1..${#channel_names}}; do
    disps+=("${(r:$max_name_len:: :)channel_names[$channel_index]}  [$channel_infos[$channel_index]]")
  done

  # Add completions
  expl=

  if __lttng_style_is_verbose channel channels; then
    # Verbose mode (list with infos).
    #
    # Using `-2` as Linux kernel and user space channels may have the
    # same name, but we want to show the different infos.
    _wanted -C channel channels expl 'channel name' \
      compadd "$@" -2 -d disps -l -a channel_names
  else
    # Just the channel names, no duplicates
    _wanted -C channel channels expl 'channel name' \
      compadd "$@" -a channel_names
  fi
}

# Adds completions for instrumentation point names.
#
# This function relies on the tracing domain options of `opt_args` and
# `opt_args[ip---syscall]` to restrict the offered completions.
__lttng_complete_ip_name() {
  local msg
  local -a list_opts

  if __lttng_kernel_domain_opt_is_set; then
    list_opts=(-k)

    if (($+opt_args[ip---syscall])); then
      msg='system call name (no `sys_` prefix)'
      list_opts+=(--syscall)
    else
      msg='Linux kernel tracepoint name'
    fi
  elif __lttng_user_domain_opt_is_set; then
    msg='user space tracepoint name'
    list_opts=(-u)
  elif __lttng_jul_domain_opt_is_set; then
    msg='`java.util.logging` logger name'
    list_opts=(-j)
  elif __lttng_log4j1_domain_opt_is_set; then
    msg='Apache log4j 1.x logger name'
    list_opts=(-l)
  elif __lttng_log4j2_domain_opt_is_set; then
    msg='Apache Log4j 2 logger name'
    list_opts=(--log4j2)
  elif __lttng_python_domain_opt_is_set; then
    msg='Python logger name'
    list_opts=(-p)
  else
    # No tracing domain option
    _guard '^-*' 'instrumentation point or recording event rule name'
    return
  fi

  # Get XML list of available instrumentation point names
  if ! __lttng_mi_run $msg list $list_opts; then
    return 0
  fi

  local list_xml=$REPLY

  # Convert to one instrumentation point name per line
  local ip_names

  ip_names=$(__lttng_xmllint_xpath $list_xml '//*[local-name()="event"]//*[local-name()="name"]/text()')

  if (($? != 0)) || [[ -z $ip_names ]]; then
    # `xmllint` error or no instrumentation points
    _guard '^-*' $msg
    return
  fi

  local ip_name
  local -a items

  while read -r ip_name; do
    if [[ -z $ip_name ]]; then
      continue
    fi

    items+=($ip_name)
  done <<< $ip_names

  # Add completions
  expl=
  _wanted instrumentation-points expl $msg compadd "$@" -a items
}

# Adds completions for the arguments of the `add-context` command.
__lttng_complete_add_context_cmd() {
  specs=(
    $help_opt_specs
    '(- : *)--list[list the available context field types]'
    '(--list -s --session)'{-s+,--session=}'[select a specific recording session]: : __lttng_complete_session_name inactive all'
    '(--list -c --channel)'{-c+,--channel=}'[select a specific channel]: : __lttng_complete_channel_name enabled opt'
    '(--list)*'{-t+,--type=}'[add a context field to be recorded]: : __lttng_complete_context_type'
    + '(domain)'
    '(--list)'{-k,--kernel}'[select the Linux kernel tracing domain]'
    '(--list)'{-u,--userspace}'[select the user space tracing domain]'
  )

  # The `java.util.logging` and Apache log4j 1.x tracing domain options
  # require LTTng-tools ≥ 2.8 (for application-specific context field
  # types).
  if ((minor_version >= 8)); then
    specs+=(
      '(--list)'{-j,--jul}'[select the `java.util.logging` tracing domain]'
      '(--list)'{-l,--log4j}'[select the Apache log4j 1.x tracing domain]'
    )
  fi

  # The Apache Log4j 2 tracing domain option requires LTTng-tools ≥ 2.14
  # (for application-specific context field types).
  if ((minor_version >= 14)); then
    specs+=(
      '(--list)--log4j2[select the Apache Log4j 2 tracing domain]'
    )
  fi

  # Add completions
  _arguments -s -W : $specs
}

# Usage:
#
#     __lttng_complete_log_level TITLE
#
# Adds completions for log level names/rule.
#
# This function relies on the tracing domain options of `opt_args` to
# restrict the offered completions.
#
# TITLE is the title of the message to show.
__lttng_complete_log_level() {
  local -r title=$1
  local -a log_levels

  # Fill the `log_levels` array depending on the tracing domain option
  if __lttng_user_domain_opt_is_set; then
    log_levels=(
      EMERG
      ALERT
      CRIT
      ERR
      WARNING
      NOTICE
      INFO
      DEBUG_SYSTEM
      DEBUG_PROGRAM
      DEBUG_PROCESS
      DEBUG_MODULE
      DEBUG_UNIT
      DEBUG_FUNCTION
      DEBUG_LINE
      DEBUG
    )
  elif __lttng_jul_domain_opt_is_set; then
    log_levels=(
      OFF
      SEVERE
      WARNING
      INFO
      CONFIG
      FINE
      FINER
      FINEST
      ALL
    )
  elif __lttng_log4j1_domain_opt_is_set || __lttng_log4j2_domain_opt_is_set; then
    log_levels=(
      OFF
      FATAL
      ERROR
      WARN
      INFO
      DEBUG
      TRACE
      ALL
    )
  elif __lttng_python_domain_opt_is_set; then
    log_levels=(
      CRITICAL
      ERROR
      WARNING
      INFO
      DEBUG
      NOTSET
    )
  else
    # No tracing domain option
    _guard '^-*' "$title (no tracing domain option found)"
    return
  fi

  # Add completions
  expl=
  _wanted log-levels expl $title compadd -a log_levels -o nosort
}

# Forwards to __lttng_complete_log_level() when completing the
# log level options of the `enable-event` command.
__lttng_complete_log_level_name() {
  __lttng_complete_log_level "log level name"
}

# Adds completions for a trigger rate policy.
__lttng_complete_trigger_rate_policy() {
  local -r types=(
    'once-after:execute the action once after N execution requests'
    'every:execute the action every N execution requests'
  )

  # Add completions
  _describe 'trigger rate policy type' types -S ':'
}

# Tries to complete common trigger action options (`--rate-policy`
# and `--action`).
#
# Uses the `in_opt_val` parameter.
#
# Sets `opt_completed` to `1` if an option was recognized and completion
# attempted, or `0` otherwise.
#
# Returns the status of the completion function when `opt_completed`
# is `1`.
__lttng_try_complete_action_opt() {
  opt_completed=1

  if [[ $in_opt_val == rate-policy ]] || compset -P '--rate-policy=*:'; then
    _message -e descriptions 'execution request count'
    return
  elif [[ $in_opt_val == rate-policy ]] || compset -P '--rate-policy='; then
    __lttng_complete_trigger_rate_policy
    return
  elif [[ $in_opt_val == action ]] || compset -P '--action='; then
    __lttng_complete_trigger_action_type
    return
  fi

  opt_completed=0
}

# Adds completions for a `notify` trigger action.
__lttng_complete_trigger_action_notify() {
  # Check all the next words in order up to (and excluding) the
  # current one.
  for word_index in {$word_index..$#words}; do
    if ((word_index == CURRENT)); then
      break
    fi

    word=$words[word_index]

    case $word in
      --rate-policy=*)
        __lttng_exclude_opts --rate-policy
        ;;
      --rate-policy)
        # Next: value of `--rate-policy` option
        in_opt_val=rate-policy
        ;;
      --action=*)
        # Defer to action handling dispatch. Action type is what
        # follows `--action=` here.
        ((word_index++))
        __lttng_complete_trigger_action ${word#--action=}
        return
        ;;
      --action)
        # Next: value of `--action` option
        in_opt_val=action
        ;;
      *)
        # Anything else: check for option value, or ignore otherwise
        case $in_opt_val in
          rate-policy)
            __lttng_exclude_opts --rate-policy
            ;;
          action)
            # Defer to action handling dispatch. Action type is
            # the whole current word.
            ((word_index++))
            __lttng_complete_trigger_action $word
            return
            ;;
        esac

        # Next: not the value of any known option
        in_opt_val=
        ;;
    esac
  done

  # Try to complete common action options
  __lttng_try_complete_action_opt

  local -ir ret=$?

  if ((opt_completed)); then
    return $ret
  fi

  # Offer all possible options
  _describe -O option long_opts -qS=
}

# Adds completions for a `start-session`, `stop-session`, or
# `rotate-session` trigger action.
__lttng_complete_trigger_action_session() {
  local -i has_session_name=0

  # Check all the next words in order up to (and excluding) the
  # current one.
  for word_index in {$word_index..$#words}; do
    if ((word_index == CURRENT)); then
      break
    fi

    word=$words[word_index]

    case $word in
      --rate-policy=*)
        __lttng_exclude_opts --rate-policy
        ;;
      --rate-policy)
        # Next: value of `--rate-policy` option
        in_opt_val=rate-policy
        ;;
      --action=*)
        # Defer to action handling dispatch. Action type is what
        # follows `--action=` here.
        ((word_index++))
        __lttng_complete_trigger_action ${word#--action=}
        return
        ;;
      --action)
        # Next: value of `--action` option
        in_opt_val=action
        ;;
      *)
        # Anything else: check for option value, or ignore otherwise
        case $in_opt_val in
          rate-policy)
            __lttng_exclude_opts --rate-policy
            ;;
          action)
            # Defer to action handling dispatch. Action type is
            # the whole current word.
            ((word_index++))
            __lttng_complete_trigger_action $word
            return
            ;;
          *)
            # Positional argument: session name
            has_session_name=1
            ;;
        esac

        # Next: not the value of any known option
        in_opt_val=
        ;;
    esac
  done

  # Try to complete common action options
  __lttng_try_complete_action_opt

  local -ir ret=$?

  if ((opt_completed)); then
    return $ret
  fi

  if [[ $PREFIX != -* ]] && ((! has_session_name)); then
    # Recording session name is the first positional argument
    __lttng_complete_session_name all all
    return
  fi

  # Offer all possible options
  _describe -O option long_opts -qS=
}

# Adds completions for a `snapshot-session` trigger action.
__lttng_complete_trigger_action_snapshot_session() {
  local session_name

  # Append specific options to `long_opts` and `short_opts`
  __lttng_add_opts --name -n 'set the snapshot output name'
  __lttng_add_opts --max-size -m 'set the maximum total size of all snapshot files'
  __lttng_add_opts --path 'set the local output destination directory'
  __lttng_add_opts --url 'set the output URL'
  __lttng_add_opts --ctrl-url 'set the control URL'
  __lttng_add_opts --data-url 'set the trace data output URL'

  # Check all the next words in order up to (and excluding) the
  # current one.
  for word_index in {$word_index..$#words}; do
    if ((word_index == CURRENT)); then
      break
    fi

    word=$words[word_index]

    case $word in
      --name=*)
        __lttng_exclude_opts --name -n
        ;;
      --name|-n)
        # Next: value of `--name`/`-n` option
        in_opt_val=name
        ;;
      --max-size=*)
        __lttng_exclude_opts --max-size -m
        ;;
      --max-size|-m)
        # Next: value of `--max-size`/`-m` option
        in_opt_val=max-size
        ;;
      --path=*)
        __lttng_exclude_opts --path --url --ctrl-url --data-url
        ;;
      --path)
        # Next: value of `--path` option
        in_opt_val=path
        ;;
      --url=*)
        __lttng_exclude_opts --path --url --ctrl-url --data-url
        ;;
      --url)
        # Next: value of `--url` option
        in_opt_val=url
        ;;
      --ctrl-url=*)
        __lttng_exclude_opts --path --url --ctrl-url
        ;;
      --ctrl-url)
        # Next: value of `--ctrl-url` option
        in_opt_val=ctrl-url
        ;;
      --data-url=*)
        __lttng_exclude_opts --path --url --data-url
        ;;
      --data-url)
        # Next: value of `--data-url` option
        in_opt_val=data-url
        ;;
      --rate-policy=*)
        __lttng_exclude_opts --rate-policy
        ;;
      --rate-policy)
        # Next: value of `--rate-policy` option
        in_opt_val=rate-policy
        ;;
      --action=*)
        # Defer to action handling dispatch. Action type is what
        # follows `--action=` here.
        ((word_index++))
        __lttng_complete_trigger_action ${word#--action=}
        return
        ;;
      --action)
        # Next: value of `--action` option
        in_opt_val=action
        ;;
      *)
        # Anything else: check for option value, or ignore otherwise
        case $in_opt_val in
          name)
            __lttng_exclude_opts --name -n
            ;;
          max-size)
            __lttng_exclude_opts --max-size -m
            ;;
          path)
            __lttng_exclude_opts --path --url --ctrl-url --data-url
            ;;
          url)
            __lttng_exclude_opts --path --url --ctrl-url --data-url
            ;;
          ctrl-url)
            __lttng_exclude_opts --path --url --ctrl-url
            ;;
          data-url)
            __lttng_exclude_opts --path --url --data-url
            ;;
          rate-policy)
            __lttng_exclude_opts --rate-policy
            ;;
          action)
            # Defer to action handling dispatch. Action type is
            # the whole current word.
            ((word_index++))
            __lttng_complete_trigger_action $word
            return
            ;;
          *)
            # Set recording session name from positional argument
            session_name=$word
        esac

        # Next: not the value of any known option
        in_opt_val=
        ;;
    esac
  done

  # Try to complete common action options
  __lttng_try_complete_action_opt

  local -ir ret=$?

  if ((opt_completed)); then
    return $ret
  fi

  # Try to offer option-specific completions
  if [[ $in_opt_val == name ]] || compset -P '--name='; then
    _message -e descriptions 'snapshot output name'
    return
  elif [[ $in_opt_val == max-size ]] || compset -P '--max-size='; then
    _message -e descriptions 'maximum size (bytes; `k`/`M`/`G` suffixes supported)'
    return
  elif [[ $in_opt_val == path ]] || compset -P '--path='; then
    _files -/
    return
  elif [[ $in_opt_val == url ]] || compset -P '--url='; then
    _message -e descriptions 'output URL'
    return
  elif [[ $in_opt_val == ctrl-url ]] || compset -P '--ctrl-url='; then
    _message -e descriptions 'control URL'
    return
  elif [[ $in_opt_val == data-url ]] || compset -P '--data-url='; then
    _message -e descriptions 'trace data output URL'
    return
  elif [[ $PREFIX != -* && -z $session_name ]]; then
    # Recording session name is the first positional argument
    __lttng_complete_session_name all snapshot
    return
  fi

  # Offer all possible options
  _describe -O option long_opts -qS= -- short_opts
}

# Usage:
#
#     __lttng_complete_trigger_action ACTIONTYPE
#
# Adds completions for a trigger action of type ACTIONTYPE.
__lttng_complete_trigger_action() {
  local -r action_type=$1
  local -i opt_completed

  # Common initial state for all action completion functions
  in_opt_val=
  long_opts=()
  short_opts=()
  __lttng_add_opts --rate-policy 'set the rate policy'
  __lttng_add_opts --action 'set the type of the next trigger action'

  case $action_type in
    notify)
      __lttng_complete_trigger_action_notify
      return
      ;;
    (start|stop|rotate)-session)
      __lttng_complete_trigger_action_session
      return
      ;;
    snapshot-session)
      __lttng_complete_trigger_action_snapshot_session
      return
      ;;
    *)
      _message "unknown action type \`$action_type\`"
      return
      ;;
  esac
}

# Adds completions for a trigger action type.
__lttng_complete_trigger_action_type() {
  local -r types=(
    'notify:send a notification through the notification mechanism'
    'start-session:start a recording session'
    'stop-session:stop a recording session'
    'rotate-session:archive the current trace chunk of a recording session'
    'snapshot-session:take a recording session snapshot'
  )

  # Add completions
  _describe 'trigger action type' types
}

# Adds completions for a new-style tracing domain type.
__lttng_complete_new_domain_type() {
  local -r types=(
    'kernel:Linux kernel'
    'user:user space tracing'
    'jul:`java.util.logging`'
    'log4j:Apache log4j 1.x'
    'log4j2:Apache Log4j 2'
    'python:Python'
  )

  # Add completions
  _describe 'tracing domain type' types
}

# Usage:
#
#     __lttng_set_opt_args_from_new_domain_type DOMAINTYPE
#
# Sets an entry of the `opt_args` associative array from the new-style
# tracing domain type name DOMAINTYPE.
__lttng_set_opt_args_from_new_domain_type() {
  local -r domain_type=$1

  case $domain_type in
    kernel*|*probe*|*syscall*) opt_args[-k]=1;;
    user*) opt_args[-u]=1;;
    jul*) opt_args[-j]=1;;
    log4j*) opt_args[-l]=1;;
    log4j2*) opt_args[--log4j2]=1;;
    python*) opt_args[-p]=1;;
  esac
}

# Usage:
#
#     __lttng_complete_inst_point_type EXCLKERNEL EXCLPROBE ONLYPROBE
#
# Adds completions for a new-style instrumentation point type.
#
# EXCLKERNEL:
#     `1` to exclude Linux kernel types.
#
# EXCLPROBE:
#     `1` to exclude probe types.
#
# ONLYPROBE:
#     `1` to _only_ offer probe types.
__lttng_complete_inst_point_type() {
  local -ir exclude_kernel_types=$1
  local -ir exclude_probe_types=$2
  local -ir only_probe_types=$3
  local -a types
  local -r probe_types=(
    {'kernel\:',}'kprobe:Linux kprobe'
    {'kernel\:',}'uprobe:Linux user space probe'
  )

  if ((only_probe_types)); then
    # Only offer `kernel:kprobe` and `kernel:uprobe`
    types=($probe_types)
  else
    # Start with non-kernel types (always available)
    if ((! exclude_kernel_types)); then
      types+=(
        kernel{,'\:tracepoint'}':LTTng kernel tracepoint'
        {kernel,}'syscall\:entry:Linux system call entry'
        {kernel,}'syscall\:exit:Linux system call exit'
        {'kernel\:',}syscall{'\:entry+exit',}':Linux system call entry and exit'
      )

      if ((! exclude_probe_types)); then
        types+=($probe_types)
      fi
    fi

    types+=(
      user{,'\:tracepoint'}':LTTng user space tracepoint'
      jul{,'\:logging'}':`java.util.logging` logging statement'
      log4j{,'\:logging'}':Apache log4j 1.x logging statement'
      log4j2{,'\:logging'}':Apache Log4j 2 logging statement'
      python{,'\:logging'}':Python logging statement'
    )
  fi

  # Add completions
  _describe 'instrumentation point type' types -o nosort
}

# Adds completions for an `event-rule-matches` trigger condition.
__lttng_complete_trigger_cond_event_rule_matches() {
  local ip_type
  local -i has_exclude_name_or_log_level_opt=0
  local -i has_filter_or_name_opt=0
  local -i has_location_or_event_name_opt=0

  __lttng_add_opts --type -t 'set the instrumentation point type'
  __lttng_add_opts --name -n 'set the instrumentation point name pattern'
  __lttng_add_opts --exclude-name -x 'add an instrumentation point name exclusion pattern'
  __lttng_add_opts --log-level -l 'set the log level rule'
  __lttng_add_opts --filter -f 'set the filter expression'
  __lttng_add_opts --location -L 'set the probe location'
  __lttng_add_opts --event-name -E 'set the event name'
  __lttng_add_opts --capture 'add a capture descriptor'

  # Check all the next words in order up to (and excluding) the
  # current one.
  for word_index in {$word_index..$#words}; do
    if ((word_index == CURRENT)); then
      break
    fi

    word=$words[word_index]

    case $word in
      --type=*)
        __lttng_exclude_opts --type -t

        # Save the event rule type
        ip_type=${word#--type=}
        ;;
      --type|-t)
        # Next: value of `--type`/`-t` option
        in_opt_val=type
        ;;
      --name=*)
        has_filter_or_name_opt=1
        __lttng_exclude_opts --name -n
        ;;
      --name|-n)
        # Next: value of `--name`/`-n` option
        in_opt_val=name
        ;;
      --exclude-name=*)
        has_exclude_name_or_log_level_opt=1
        ;;
      --exclude-name|-x)
        # Next: value of `--exclude-name`/`-x` option
        in_opt_val=exclude-name
        ;;
      --log-level=*)
        has_exclude_name_or_log_level_opt=1
        __lttng_exclude_opts --log-level -l
        ;;
      --log-level|-l)
        # Next: value of `--log-level`/`-l` option
        in_opt_val=log-level
        ;;
      --filter=*)
        has_filter_or_name_opt=1
        __lttng_exclude_opts --filter -f
        ;;
      --filter|-f)
        # Next: value of `--filter`/`-f` option
        in_opt_val=filter
        ;;
      --location=*)
        has_location_or_event_name_opt=1
        __lttng_exclude_opts --location -L
        ;;
      --location|-L)
        # Next: value of `--location`/`-L` option
        in_opt_val=location
        ;;
      --event-name=*)
        has_location_or_event_name_opt=1
        __lttng_exclude_opts --event-name -E
        ;;
      --event-name|-E)
        # Next: value of `--event-name`/`-E` option
        in_opt_val=event-name
        ;;
      --capture)
        # Next: value of `--capture` option
        in_opt_val=capture
        ;;
      --action=*)
        # Defer to action handling dispatch. Action type is what
        # follows `--action=` here.
        ((word_index++))
        __lttng_complete_trigger_action ${word#--action=}
        return
        ;;
      --action)
        # Next: value of `--action` option
        in_opt_val=action
        ;;
      *)
        # Anything else: check for option value, or ignore otherwise
        case $in_opt_val in
          type)
            __lttng_exclude_opts --type -t

            # Save the event rule type
            ip_type=$word
            ;;
          name)
            has_filter_or_name_opt=1
            __lttng_exclude_opts --name -n
            ;;
          exclude-name)
            has_exclude_name_or_log_level_opt=1;;
          log-level)
            has_exclude_name_or_log_level_opt=1
            __lttng_exclude_opts --log-level -l
            ;;
          filter)
            has_filter_or_name_opt=1
            __lttng_exclude_opts --filter -f
            ;;
          location)
            has_location_or_event_name_opt=1
            __lttng_exclude_opts --location -L
            ;;
          event-name)
            has_location_or_event_name_opt=1
            __lttng_exclude_opts --event-name -E
            ;;
          action)
            # Defer to action handling dispatch. Action type is
            # the whole current word.
            ((word_index++))
            __lttng_complete_trigger_action $word
            return
            ;;
        esac

        # Next: not the value of any known option
        in_opt_val=0
        ;;
    esac
  done

  # Exclude groups of options
  if ((has_filter_or_name_opt || has_exclude_name_or_log_level_opt)); then
    # Remove probe options from possible options
    __lttng_exclude_opts --event-name -E --location -L
  fi

  if ((has_location_or_event_name_opt)); then
    # Remove non-probe options from possible options
    __lttng_exclude_opts --filter -f --log-level -l --name -n --exclude-name -x
  fi

  # Filter options based on `--type`/`-t`
  case $ip_type in
    kernel(|:tracepoint|:syscall*))
      __lttng_exclude_opts --exclude-name -x --log-level -l --location -L --event-name -E
      ;;
    *probe*)
      __lttng_exclude_opts --name -n --exclude-name -x --log-level -l --filter -f
      ;;
    user*)
      __lttng_exclude_opts --location -L --event-name -E
      ;;
    *jul*|*log4j*|*python*|*logging*)
      __lttng_exclude_opts --exclude-name -x --location -L --event-name -E
      ;;
  esac

  # Try to offer option-specific completions
  if [[ $in_opt_val == type ]] || compset -P '--type='; then
    __lttng_complete_inst_point_type \
      $has_exclude_name_or_log_level_opt \
      $has_filter_or_name_opt \
      $has_location_or_event_name_opt
    return
  elif [[ $in_opt_val == name ]] || compset -P '--name='; then
    _message -e descriptions 'instrumentation point name pattern'
    return
  elif [[ $in_opt_val == exclude-name ]] || compset -P '--exclude-name='; then
    _message -e descriptions 'instrumentation point name exclusion pattern'
    return
  elif [[ $in_opt_val == log-level ]] || compset -P '--log-level='; then
    # __lttng_complete_log_level() relies on `opt_args` for the
    # tracing domain.
    __lttng_set_opt_args_from_new_domain_type $ip_type
    __lttng_complete_log_level 'log level rule'
    return
  elif [[ $in_opt_val == filter ]] || compset -P '--filter='; then
    _message -e descriptions 'filter expression'
    return
  elif [[ $in_opt_val == location ]] || compset -P '--location='; then
    case $ip_type in
      *kprobe*)
        _message -e descriptions 'Linux kprobe location'
        return
        ;;
      *uprobe*)
        _message -e descriptions 'Linux user space probe location'
        return
        ;;
      *)
        _message -e descriptions 'probe location'
        return
        ;;
    esac
  elif [[ $in_opt_val == event-name ]] || compset -P '--event-name='; then
    _message -e descriptions 'event name'
    return
  elif [[ $in_opt_val == capture ]] || compset -P '--capture='; then
    _message -e descriptions 'capture descriptor'
    return
  elif [[ $in_opt_val == action ]] || compset -P '--action='; then
    __lttng_complete_trigger_action_type
    return
  else
    # Offer all possible options
    _describe -O option long_opts -qS= -- short_opts
    return
  fi

  return 1
}

# Usage:
#
#     __lttng_complete_trigger_cond_simple WITHCHANDOMAIN WITHRATIO WITHSIZE
#
# Adds completions for a "simple" trigger condition (anything except
# `event-rule-matches`).
#
# WITHCHANDOMAIN:
#     `1` to handle channel and tracing domain options.
#
# WITHRATIO:
#     `1` to handle a `--threshold-ratio` option.
#
# WITHSIZE:
#     `1` to handle `--threshold-size`/`-t` options.
__lttng_complete_trigger_cond_simple() {
  local -ir with_channel_domain_opts=$1
  local -ir with_threshold_ratio_opt=$2
  local -ir with_threshold_size_opts=$3
  local session_name
  local domain_type

  __lttng_add_opts --session -s 'set the target recording session name'

  if ((with_channel_domain_opts)); then
    __lttng_add_opts --channel -c 'set the target channel name'
    __lttng_add_opts --domain -d 'set the target tracing domain'
  fi

  if ((with_threshold_ratio_opt)); then
    __lttng_add_opts --threshold-ratio 'set the threshold as a ratio of the total buffer size'
  fi

  if ((with_threshold_size_opts)); then
    __lttng_add_opts --threshold-size -t 'set the threshold size'
  fi

  # Check all the next words in order up to (and excluding) the
  # current one.
  for word_index in {$word_index..$#words}; do
    if ((word_index == CURRENT)); then
      break
    fi

    word=$words[word_index]

    case $word in
      --session=*)
        __lttng_exclude_opts --session -s

        # Save the recording session name
        session_name=${word#--session=}
        ;;
      --session|-s)
        # Next: value of `--session`/`-s` option
        in_opt_val=session;;
      --channel=*)
        __lttng_exclude_opts --channel -c
        ;;
      --channel|-c)
        # Next: value of `--channel`/`-c` option
        in_opt_val=channel;;
      --domain=*)
        __lttng_exclude_opts --domain -d

        # Save the tracing domain type name
        domain_type=${word#--domain=}
        ;;
      --domain|-d)
        # Next: value of `--domain`/`-d` option
        in_opt_val=domain;;
      --threshold-(size|ratio)=*)
        __lttng_exclude_opts --threshold-size --threshold-ratio -t
        ;;
      --threshold-size|-t)
        # Next: value of `--threshold-size`/`-t` option
        in_opt_val=threshold-size;;
      --threshold-ratio)
        # Next: value of `--threshold-ratio` option
        in_opt_val=threshold-ratio;;
      --action=*)
        # Defer to action handling dispatch. Action type is what
        # follows `--action=` here.
        ((word_index++))
        __lttng_complete_trigger_action ${word#--action=}
        return
        ;;
      --action)
        # Next: value of `--action` option
        in_opt_val=action;;
      *)
        # Anything else: check for option value, or ignore otherwise
        case $in_opt_val in
          session)
            __lttng_exclude_opts --session -s

            # Save the recording session name
            session_name=$word
            ;;
          channel)
            __lttng_exclude_opts --channel -c
            ;;
          domain)
            __lttng_exclude_opts --domain -d

            # Save the tracing domain type
            domain_type=$word
            ;;
          threshold-*)
            __lttng_exclude_opts --threshold-size --threshold-ratio -t
            ;;
          action)
            # Defer to action handling dispatch. Action type is
            # the whole current word.
            ((word_index++))
            __lttng_complete_trigger_action $word
            return
            ;;
        esac

        # Next: not the value of any known option
        in_opt_val=
        ;;
    esac
  done

  # Try to offer option-specific completions
  if [[ $in_opt_val == session ]] || compset -P '--session='; then
    __lttng_complete_session_name all all
    return
  elif [[ $in_opt_val == channel ]] || compset -P '--channel='; then
    if ((with_channel_domain_opts)); then
      # __lttng_complete_channel_name() relies on `line[1]` for the
      # recording session name and on `opt_args` for the
      # tracing domain.
      line=($session_name)
      opt_args=()
      __lttng_set_opt_args_from_new_domain_type $domain_type
      __lttng_complete_channel_name all arg
      return
    fi
  elif [[ $in_opt_val == domain ]] || compset -P '--domain='; then
    if ((with_channel_domain_opts)); then
      __lttng_complete_new_domain_type
      return
    fi
  elif [[ $in_opt_val == threshold-size ]] || compset -P '--threshold-size='; then
    if ((with_threshold_size_opts)); then
      _message -e descriptions 'threshold size (bytes; `k`/`M`/`G` suffixes supported)'
      return
    fi
  elif [[ $in_opt_val == threshold-ratio ]] || compset -P '--threshold-ratio='; then
    if ((with_threshold_ratio_opt)); then
      _message -e descriptions 'threshold ratio (0 to 1)'
      return
    fi
  elif [[ $in_opt_val == action ]] || compset -P '--action='; then
    __lttng_complete_trigger_action_type
    return
  else
    # Offer all possible options
    _describe -O option long_opts -qS= -- short_opts
    return
  fi

  return 1
}

# Usage:
#
#     __lttng_complete_trigger_cond CONDTYPE
#
# Adds completions for a trigger condition of type CONDTYPE.
__lttng_complete_trigger_cond() {
  local -r cond_type=$1

  # Common initial state for all condition completion functions
  long_opts=()
  short_opts=()
  in_opt_val=
  __lttng_add_opts --action 'set the type of the next trigger action'

  case $cond_type in
    event-rule-matches)
      __lttng_complete_trigger_cond_event_rule_matches
      return
      ;;
    channel-buffer-usage-[lg]e)
      __lttng_complete_trigger_cond_simple 1 1 1
      return
      ;;
    session-consumed-size-ge)
      __lttng_complete_trigger_cond_simple 0 0 1
      return
      ;;
    session-rotation-(starts|finishes))
      __lttng_complete_trigger_cond_simple 0 0 0
      return
      ;;
    *)
      _message "unknown condition type \`$cond_type\`"
      return
      ;;
  esac
}

# Adds completions for a trigger condition type.
__lttng_complete_trigger_cond_type() {
  local types=(
    'event-rule-matches:an event rule matches an event'
  )

  if ((minor_version >= 15)); then
    types+=(
      'channel-buffer-usage-ge:channel buffer size becomes ≥ some threshold'
      'channel-buffer-usage-le:channel buffer size becomes ≤ some threshold'
      'session-consumed-size-ge:recording session consumed size becomes ≥ some threshold'
      'session-rotation-finishes:recording session rotation operation finishes'
      'session-rotation-starts:recording session rotation operation starts'
    )
  fi

  # Add completions
  _describe 'trigger condition type' types
}

# Adds completions for the arguments of the `add-trigger` command.
__lttng_complete_add_trigger_cmd() {
  # Shared parameters used by called functions
  local in_opt_val
  local -a long_opts short_opts
  local -i word_index
  local word

  __lttng_add_opts --name 'set the trigger name'
  __lttng_add_opts --owner-uid 'add the trigger to another Unix user'
  __lttng_add_opts --condition 'set the type of the trigger condition'

  # Check all words in order up to (and excluding) the current one
  for word_index in {1..$#words}; do
    if ((word_index == CURRENT)); then
      break
    fi

    word=$words[word_index]

    case $word in
      -h|--help)
        # Exit now: nothing to complete if help is wanted
        return 1;;
      --name=*)
        __lttng_exclude_opts --name;;
      --name)
        # Next: value of `--name` option
        in_opt_val=name;;
      --owner-uid=*)
        __lttng_exclude_opts --owner-uid;;
      --owner-uid)
        # Next: value of `--owner-uid` option
        in_opt_val=owner-uid;;
      --condition=*)
        # Defer to condition handling dispatch. Condition type is what
        # follows `--condition=` here.
        ((word_index++))
        __lttng_complete_trigger_cond ${word#--condition=}
        return
        ;;
      --condition)
        # Next: value of `--condition` option
        in_opt_val=condition;;
      *)
        # Anything else: check for option value, or ignore otherwise
        case $in_opt_val in
          name)
            __lttng_exclude_opts --name;;
          owner-uid)
            __lttng_exclude_opts --owner-uid;;
          condition)
            # Defer to condition handling dispatch. Condition type is
            # the whole current word.
            ((word_index++))
            __lttng_complete_trigger_cond $word
            return
            ;;
        esac

        # Next: not the value of any known option
        in_opt_val=
        ;;
    esac
  done

  # Try to offer option-specific completions
  if [[ $in_opt_val == name ]] || compset -P '--name='; then
    _message -e descriptions 'trigger name'
    return
  elif [[ $in_opt_val == owner-uid ]] || compset -P '--owner-uid='; then
    __lttng_complete_uid
    return
  elif [[ $in_opt_val == condition ]] || compset -P '--condition='; then
    __lttng_complete_trigger_cond_type
    return
  else
    # Offer all possible options
    local -r help_opts=({-h,--help}':show help')

    _describe -O option long_opts -qS= -- help_opts
    return
  fi
}

# Adds completions for the arguments of the `clear` command.
__lttng_complete_clear_cmd() {
  specs=(
    $help_opt_specs
    '(-a --all):: : __lttng_complete_session_name all all'
    '(1 -a --all)'{-a,--all}'[clear all recording sessions]'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `create` command.
__lttng_complete_create_cmd() {
  specs=(
    $help_opt_specs
    ':: :_guard "^-*" "new recording session name"'
    '(-C --ctrl-url output)'{-C+,--ctrl-url=}'[set the control URL]:control URL:'
    '(-D --data-url output)'{-D+,--data-url=}'[set the trace data output URL]:trace data output URL:'
  )

  # The `--shm-path` option requires LTTng-tools ≥ 2.7
  if ((minor_version >= 7)); then
    specs+=(
      '--shm-path=[write shared memory files to a specific directory]:shared memory file directory:_files -/'
    )
  fi

  # The `--trace-format` option requires LTTng-tools ≥ 2.15
  if ((minor_version >= 15)); then
    specs+=(
      '(-F --trace-format)'{-F+,--trace-format=}'[set the trace format]:trace format:(ctf-1.8 ctf-2 default)'
    )
  fi

  # Add the remaining option groups
  specs+=(
    + '(output)'
    '(-D --data-url -C --ctrl-url --live)--no-output[disable trace data output]'
    '(-D --data-url -C --ctrl-url --live)'{-o+,--output=}'[set the local trace data output directory]:trace data output directory:_files -/'
    '(-D --data-url -C --ctrl-url)'{-U+,--set-url=}'[set the trace data output and control URL]:trace data output and control URL:'
    + '(mode)'
    '(-o --output --no-output)--live=[create an LTTng live recording session]:live timer period (µs):'
    '--snapshot[create a snapshot recording session]'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `destroy` command.
__lttng_complete_destroy_cmd() {
  specs=(
    $help_opt_specs
  )

  # The `--no-wait` option requires LTTng-tools ≥ 2.8
  if ((minor_version >= 8)); then
    specs+=(
      '(-n --no-wait)'{-n,--no-wait}'[exit immediately]'
    )
  fi

  specs+=(
    + '(session)'
    ':: : __lttng_complete_session_name all all'
    {-a,--all}'[destroy all recording sessions]'
  )

  # The `--glob` option requires LTTng-tools ≥ 2.14
  if ((minor_version >= 14)); then
    specs+=(
      {-g+,--glob=}'[destroy recording sessions matched by a pattern]:globbing pattern:'
    )
  fi

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `disable-channel` command.
__lttng_complete_disable_channel_cmd() {
  specs=(
    $help_opt_specs
    '(-s --session)'{-s+,--session=}'[select a specific recording session]: : __lttng_complete_session_name all all'
    ': : _sequence __lttng_complete_channel_name enabled opt'
    + '(domain)'
    {-k,--kernel}'[select the Linux kernel tracing domain]'
    {-u,--userspace}'[select the user space tracing domain]'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Sets `REPLY` to the XML tracing domain name value depending on the
# tracing domain option (in `opt_args`), or prints nothing and
# returns 1 when not available.
__lttng_xml_domain_name_from_opt() {
  if __lttng_kernel_domain_opt_is_set; then
    REPLY=KERNEL
  elif __lttng_user_domain_opt_is_set; then
    REPLY=UST
  elif __lttng_jul_domain_opt_is_set; then
    REPLY=JUL
  elif __lttng_log4j1_domain_opt_is_set; then
    REPLY=LOG4J
  elif __lttng_log4j2_domain_opt_is_set; then
    REPLY=LOG4J2
  elif __lttng_python_domain_opt_is_set; then
    REPLY=PYTHON
  else
    return 1
  fi
}

# Sets `REPLY` to the XML instrumentation point type value depending on
# the instrumentation point option (in `opt_args`).
__lttng_xml_ip_type_from_opt() {
  if (($+opt_args[--syscall] || $+opt_args[ip---syscall])); then
    REPLY=SYSCALL
  elif (($+opt_args[--probe] || $+opt_args[ip---probe])); then
    REPLY=PROBE
  elif (($+opt_args[--function] || $+opt_args[ip---function])); then
    REPLY=FUNCTION
  else
    REPLY=TRACEPOINT
  fi
}

# Adds completions for an old, enabled recording event rule name
# condition (for the `disable-event` command).
__lttng_complete_old_enabled_er_name_cond() {
  local -r msg='recording event rule name condition'

  # Find the recording session name to use
  local session_name

  if (($+opt_args[-s])); then
    session_name=$opt_args[-s]
  elif (($+opt_args[--session])); then
    session_name=$opt_args[--session]
  else
    # Fall back to current recording session
    if ! __lttng_cur_session_name; then
      _guard '^-*' $msg
    fi

    session_name=$REPLY
  fi

  # Find the channel name to use (`channel0` is the default name)
  local channel_name=channel0

  if (($+opt_args[-c])); then
    channel_name=$opt_args[-c]
  elif (($+opt_args[--channel])); then
    channel_name=$opt_args[--channel]
  fi

  # Create tracing domain XPath part
  local domain_xpath

  if __lttng_xml_domain_name_from_opt; then
    domain_xpath="*[local-name() = 'domain'][*[local-name() = 'type'] = '$REPLY']//"
  fi

  # Create channel XPath part
  local -r channel_xpath="*[local-name() = 'channel'][*[local-name() = 'name'] = '$channel_name']//"

  # Create instrumentation point type XPath part
  __lttng_xml_ip_type_from_opt

  local -r ip_type_xpath="[*[local-name() = 'type'] = '$REPLY']"

  # Get XML document (detailed recording session)
  if ! __lttng_session_xml $msg $session_name; then
    return 0
  fi

  local session_xml=$REPLY

  # Convert to one recording event rule name per line
  local lines

  if ! lines=$(__lttng_xmllint_xpath $session_xml "//$domain_xpath${channel_xpath}*[local-name() = 'event']${ip_type_xpath}[*[local-name() = 'enabled'] = 'true']/*[local-name() = 'name']/text()"); then
    _guard '^-*' $msg
    return
  fi

  local line
  local -a er_names

  while read -r line; do
    if [[ -z ${line// } ]]; then
      # Skip empty line
      continue
    fi

    er_names+=$line
  done <<< $lines

  # Add completions
  expl=
  _wanted -C event events expl $msg compadd "$@" -a er_names
}

# Adds completions for the arguments of the `disable-event` command.
__lttng_complete_disable_event_cmd() {
  local -r kernel_ip_opt_excl=(--syscall --probe --function)

  specs=(
    $help_opt_specs
    '(-s --session)'{-s+,--session=}'[select a specific recording session]: : __lttng_complete_session_name all all'
    '(-c --channel)'{-c+,--channel=}'[select a specific channel]: : __lttng_complete_channel_name all opt'
    + '(names)'
    {-a,--all-events}'[disable all recording event rules]'
    ': :_sequence __lttng_complete_old_enabled_er_name_cond'
    + '(domain)'
    {-k,--kernel}'[select the Linux kernel tracing domain]'
    "($kernel_ip_opt_excl)"{-u,--userspace}'[select the user space tracing domain]'
    "($kernel_ip_opt_excl)"{-j,--jul}'[select the `java.util.logging` tracing domain]'
  )

  # Add tracing domain option specifications based on the minor version
  # of LTTng-tools.
  if ((minor_version >= 6)); then
    specs+=(
      "($kernel_ip_opt_excl)"{-l,--log4j}'[select the Apache log4j 1.x tracing domain]'
    )
  fi

  if ((minor_version >= 14)); then
    specs+=(
      "($kernel_ip_opt_excl)--log4j2[select the Apache Log4j 2 tracing domain]"
    )
  fi

  if ((minor_version >= 7)); then
    specs+=(
      "($kernel_ip_opt_excl)"{-p,--python}'[select the Python tracing domain]'
    )
  fi

  # Add instrumentation point type option specifications based on the
  # minor version of LTTng-tools.
  if ((minor_version >= 6)); then
    specs+=(
      + '(ip)'
      "($non_kernel_domain_opt_excl)--syscall[disable recording ER matching Linux system call events]"
    )
  fi

  if ((minor_version >= 7)); then
    specs+=(
      '--tracepoint[disable recording ER matching tracepoint events]'
      "($non_kernel_domain_opt_excl)--probe[disable recording ER matching kprobe events]"
      "($non_kernel_domain_opt_excl)--function[disable recording ER matching kretprobe events]"
    )
  fi

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `disable-rotation` command.
__lttng_complete_disable_rotation_cmd() {
  specs=(
    $help_opt_specs
    '(-s --session)'{-s+,--session=}'[select a specific recording session]: : __lttng_complete_session_name all all'
    '--timer[disable the periodic rotation schedule]'
    '--size[disable the size-based rotation schedule]'
  )

  _arguments -s -W : $specs
}

# Adds completions for a channel output type (`mmap` and, possibly,
# `splice`) (for the `--output` option of the `enable-channel` command).
#
# This function replaces the argument field of the current context with
# `output-opt`.
#
# This function relies on the user space tracing domain options of
# `opt_args` to restrict the offered completions. Without the user
# space tracing domain option, this function adds the `splice`
# completion too.
__lttng_complete_channel_output_type() {
  local output_types=(mmap)

  if ! __lttng_user_domain_opt_is_set; then
    # Linux kernel tracing domain or none
    output_types+=(splice)
  fi

  expl=
  _wanted -C 'output-opt' values expl 'output type' compadd -a output_types
}

# Adds completions for the non-option argument of the `enable-channel`
# command.
#
# This function either, depending on the keys of `opt_args`:
#
# At least one creation option:
#     Shows a message to enter the new channel name.
#
# Otherwise:
#     Adds completions for a comma-separated list of known channel names
#     using __lttng_complete_channel_name().
__lttng_complete_enable_channel_cmd_names() {
  local key
  local -r enable_opts=(
    -s --session
    domain---kernel domain--k
    domain---userspace domain--u
  )

  # For each key of `opt_args`
  for key in "${(@k)opt_args}"; do
    if (($enable_opts[(Ie)$key])); then
      # Enabling option exists: skip
      continue
    fi

    # Creation option exists: single name
    _guard '^-*' 'new channel name'
    return
  done

  # Comma-separated list of existing channel names
  _sequence __lttng_complete_channel_name disabled opt
}

# Adds completions for a buffer ownership.
__lttng_complete_buffer_ownership() {
  local -r kernel_choices=(
    'system:use global ring buffers'
  )
  local -r user_choices=(
    'user:use per-user ring buffers'
    'process:use per-process ring buffers'
  )
  local -a choices

  if __lttng_kernel_domain_opt_is_set; then
    choices=($kernel_choices)
  elif __lttng_user_domain_opt_is_set; then
    choices=($user_choices)
  else
    choices=($kernel_choices $user_choices)
  fi

  # Add completions
  _describe 'buffer ownership' choices
}

# Adds completions for a buffer allocation policy.
__lttng_complete_buffer_alloc() {
  local choices=(
    'per-cpu:allocate sub-buffers for each CPU'
  )

  if __lttng_user_domain_opt_is_set || ! __lttng_kernel_domain_opt_is_set; then
    choices+=('per-channel:allocate sub-buffers for each channel')
  fi

  # Add completions
  _describe 'buffer allocation policy' choices
}

# Adds completions for an automatic memory reclaim strategy.
__lttng_complete_auto_reclaim_memory() {
  if compset -P older-than:; then
    _message -e descriptions 'age (µs; `ms`/`s`/`m`/`h` suffixes supported)'
    return
  fi

  local -r strategies=(
    'off:no automatic memory reclamation'
    'consumed:reclaim memory as soon as sub-buffers are consumed'
  )
  local -r strategies_with_arg=(
    'older-than:reclaim memory of sub-buffers older than some age'
  )

  # Add completions
  _describe 'automatic memory reclaim strategy' strategies -- strategies_with_arg -qS:
}

# Adds completions for the arguments of the `enable-channel` command.
__lttng_complete_enable_channel_cmd() {
  specs=(
    $help_opt_specs
    '--switch-timer=[set the switch timer period]:switch timer period (µs):'
    '--read-timer=[set the read timer period]:read timer period (µs):'
    '--subbuf-size=[set the size of each sub-buffer]:sub-buffer size (bytes; `k`/`M`/`G` suffixes supported):'
    '--num-subbuf=[set the number of sub-buffers per ring buffer]:sub-buffer count:'
    '--tracefile-size=[set the maximum size of each trace file]:trace file size (bytes; `k`/`M`/`G` suffixes supported):'
    '--tracefile-count=[set the maximum number of trace files]:maximum trace file count:'
    '--output=[set the output type]: : __lttng_complete_channel_output_type'
    '(-s --session)'{-s+,--session=}'[select a specific recording session]: : __lttng_complete_session_name all all'
    ': : __lttng_complete_enable_channel_cmd_names'
  )

  # The `--blocking-timeout` and `--monitor-timer` options require
  # LTTng-tools ≥ 2.10.
  if ((minor_version >= 10)); then
    specs+=(
      '(--kernel --overwrite --buffers-global)--blocking-timeout=[set the blocking timeout]:blocking timeout (µs):'
      '--monitor-timer=[set the monitor timer period]:monitor timer period (µs):'
    )
  fi

  # The `buffer-allocation` option require LTTng-tools ≥ 2.14
  if ((minor_version >= 14)); then
    specs+=(
      '--buffer-allocation=[set the allocation policy of ring buffers]: : __lttng_complete_buffer_alloc'
    )
  fi

  # Add the remaining option groups
  specs+=(
    + '(domain)'
    '(--blocking-timeout --buffers-uid --buffers-pid --auto-reclaim-memory --buffer-preallocation --watchdog-timer)'{-k,--kernel}'[select the Linux kernel tracing domain]'
    '(--buffers-global)'{-u,--userspace}'[select the user space tracing domain]'
    + '(loss-mode)'
    '--discard[discard event records with no available sub-buffer]'
    '(--blocking-timeout --auto-reclaim-memory)--overwrite[overwrite oldest sub-buffer with no available sub-buffer]'
    + '(buffer-ownership)'
    '(-k --kernel)--buffers-uid[use per-user ring buffers]'
    '(-k --kernel)--buffers-pid[use per-process ring buffers]'
    '(-u --userspace --blocking-timeout)--buffers-global[use global ring buffers]'
  )

  # The `--buffer-ownership` option requires LTTng-tools ≥ 2.14
  if ((minor_version >= 14)); then
    specs+=(
      '--buffer-ownership=[set the ownership of ring buffers]: : __lttng_complete_buffer_ownership'
    )
  fi

  # The `--auto-reclaim-memory`, `--buffer-preallocation`,
  # and `--watchdog-timer` options require LTTng-tools ≥ 2.15
  if ((minor_version >= 15)); then
    specs+=(
      '(-k --kernel --overwrite)--auto-reclaim-memory=[set automatic memory reclamation strategy]: : __lttng_complete_auto_reclaim_memory'
      '(-k --kernel)--buffer-preallocation=[set the sub-buffer preallocation policy]:policy:(on-demand preallocate)'
      '(-k --kernel)--watchdog-timer=[set the watchdog timer period]:watchdog timer period (µs):'
    )
  fi

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `enable-event` command.
__lttng_complete_enable_event_cmd() {
  local -r kernel_opts_excl=(
    -k --kernel
    --syscall
    --probe
    --userspace-probe
    --function
  )
  local -r non_kernel_opts_excl=(
    $non_kernel_domain_opt_excl
    -x --exclude
    log-level
  )
  local -r exclude_opt_excl=(
    $kernel_opts_excl
    -j --jul
    -l --log4j
       --log4j2
    -p --python
    -x --exclude
  )
  local ip_specs=(
    '--tracepoint[only match LTTng tracepoint events]'
    "($non_kernel_opts_excl)--syscall[only match Linux system call events]"
    "($non_kernel_opts_excl)--probe=[only match kprobe events]:kprobe location:"
    "($non_kernel_opts_excl)--function=[only match kretprobe events]:kretprobe location:"
  )
  local domain_specs=(
    "($non_kernel_opts_excl)"{-k,--kernel}'[select the Linux kernel tracing domain]'
    "($kernel_opts_excl)"{-u,--userspace}'[select the user space tracing domain]'
    "($kernel_opts_excl -x --exclude)"{-j,--jul}'[select the `java.util.logging` tracing domain]'
  )

  # The Apache log4j 1.x tracing domain options require LTTng-tools ≥ 2.6
  if ((minor_version >= 6)); then
    domain_specs+=(
      "($kernel_opts_excl -x --exclude)"{-l,--log4j}'[select the Apache log4j 1.x tracing domain]'
    )
  fi

  # The Python tracing domain options require LTTng-tools ≥ 2.7
  if ((minor_version >= 7)); then
    domain_specs+=(
      "($kernel_opts_excl -x --exclude)"{-p,--python}'[select the Python tracing domain]'
    )
  fi

  # The Linux user space probe instrumentation options require LTTng-tools ≥ 2.11
  if ((minor_version >= 11)); then
    ip_specs+=(
      "($non_kernel_opts_excl)--userspace-probe=[only match Linux user space probe events]:user space probe location:"
    )
  fi

  # The Apache Log4j 2 tracing domain options require LTTng-tools ≥ 2.14
  if ((minor_version >= 14)); then
    domain_specs+=(
      "($kernel_opts_excl -x --exclude)--log4j2[select the Apache Log4j 2 tracing domain]"
    )
  fi

  # Add completions.
  #
  # There's no way, based on the command-line arguments, to distinguish
  # between creating a new recording event rule and enabling an
  # existing, disabled recording event rule here.
  #
  # For example, given this:
  #
  #     $ lttng enable-event --kernel --syscall <TAB>
  #
  # At this point, the user might want the list of existing, disabled
  # kernel system call recording event rule names (current recording
  # session, default channel name), or the full list of available system
  # call instrumentation point names.
  #
  # This function makes the arbitrary choice to provide the available
  # instrumentation point names (__lttng_complete_ip_name()) because,
  # interactively, it seems to be more common/useful than disabling
  # existing recording event rules.
  specs=(
    $help_opt_specs
    '(--probe --userspace-probe --function -f --filter)'{-f+,--filter=}'[only match events which satisfy an expression]:filter expression:'
    "($exclude_opt_excl)"{-x+,--exclude=}'[exclude event name patterns]:comma-separated list of patterns:'
    '(-s --session)'{-s+,--session=}'[select a specific recording session]: : __lttng_complete_session_name all all'
    '(-c --channel)'{-c+,--channel=}'[select a specific channel]: : __lttng_complete_channel_name all opt'
    + '(log-level)'
    "($kernel_opts_excl)--loglevel=[only match events with specific log levels]: : __lttng_complete_log_level_name"
    "($kernel_opts_excl)--loglevel-only=[only match events with an exact log level]: : __lttng_complete_log_level_name"
    + '(names)'
    {-a,--all}'[match events regardless of their name]'
    ': :_sequence __lttng_complete_ip_name'
    + '(ip)' $ip_specs
    + '(domain)' $domain_specs
  )

  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `enable-rotation` command.
__lttng_complete_enable_rotation_cmd() {
  specs=(
    $help_opt_specs
    '(-s --session)'{-s+,--session=}'[select a specific recording session]: : __lttng_complete_session_name all all'
    '--timer=[rotate periodically]:period (µs; `ms`/`s`/`m`/`h` suffixes supported):'
    '--size=[rotate based on flushed size of current trace chunk]:size (bytes; `k`/`M`/`G` suffixes supported):'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `help` command.
__lttng_complete_help_cmd() {
  specs=(
    $help_opt_specs
    ': : __lttng_complete_cmd_name'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `list` command.
__lttng_complete_list_cmd() {
  local -r domain_opt_excl=(
    -d --domain
    -f --fields
    -c --channel
    -k --kernel
    -u --userspace
    -j --jul
    -l --log4j
       --log4j2
    -p --python
       --syscall
  )

  specs=(
    $help_opt_specs
    '(-d --domain -f --fields -c --channel --mem-usage 1)'{-f,--fields}'[show user space tracepoint fields]'
    "($domain_opt_excl)"{-d,--domain}'[show tracing domains]'
    '(-d --domain -f --fields --syscall -c --channel)'{-c+,--channel=}'[list the objects of specific channel(s)]: : __lttng_complete_channel_name all arg'
    '(-d --domain -f --fields --syscall):recording session name: __lttng_complete_session_name all all'
    '(-d --domain -k --kernel)'{-k,--kernel}'[list Linux kernel tracing domain objects]'
    '(-d --domain -u --userspace)'{-u,--userspace}'[list user space tracing domain objects]'
    '(-d --domain -j --jul)'{-j,--jul}'[list `java.util.logging` tracing domain objects]'
  )

  # The Apache log4j 1.x tracing domain and `--syscall` options require
  # LTTng-tools ≥ 2.6.
  if ((minor_version >= 6)); then
    specs+=(
      '(-d --domain -l --log4j)'{-l,--log4j}'[list Apache log4j 1.x tracing domain objects]'
      '(-d --domain -f --fields -c --channel --mem-usage 1)--syscall[list Linux kernel system calls]'
    )
  fi

  # The Python tracing domain options require LTTng-tools 2.7
  if ((minor_version >= 7)); then
    specs+=(
      '(-d --domain -p --python)'{-p,--python}'[list Python tracing domain objects]'
    )
  fi

  # The Apache Log4j 2 tracing domain requires LTTng-tools ≥ 2.14.
  if ((minor_version >= 14)); then
    specs+=(
      '(-d --domain --log4j2)--log4j2[list Apache Log4j 2 tracing domain objects]'
    )
  fi

  # The modern output options require LTTng-tools ≥ 2.15.
  if ((minor_version >= 15)); then
    specs+=(
      '(-f --fields --syscall)--mem-usage=[set channel memory usage display mode]:memory usage mode:(compact full)'
      '--style=[set command output style]:output style:(compact)'
      '--no-truncate[do not truncate long output lines]'
    )
  fi

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `list-triggers` command.
__lttng_complete_list_triggers_cmd() {
  _arguments -s -W : $help_opt_specs
}

# Adds completions for the arguments of the `load` command.
__lttng_complete_load_cmd() {
  specs=(
    $help_opt_specs
    '(-f --force)'{-f,--force}'[overwrite existing recording sessions]'
    '(-i --input-path)'{-i+,--input-path=}'[load recording session configurations from a specific path]:recording session configuration path:_files'
    '(-a --all --override-name 1)'{-a,--all}'[load all recording session configurations]'
    '(-a --all):recording session configuration name:_guard "^-*" "recording session name"'
  )

  if ((minor_version >= 9)); then
    specs+=(
      '--override-url=[override the loaded recording session output URL]:output URL:'
      '(-a --all)--override-name=[override the loaded recording session name]:recording session name:'
    )
  fi

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `metadata` command
# (deprecated).
__lttng_complete_metadata_cmd() {
  specs=(
    $help_opt_specs
    ':action:(regenerate)'
    '(-s --session)'{-s+,--session=}'[select a specific recording session]: : __lttng_complete_session_name all all'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `regenerate` command.
__lttng_complete_regenerate_cmd() {
  specs=(
    $help_opt_specs
    ':trace data type:(metadata statedump)'
    '(-s --session)'{-s+,--session=}'[select a specific recording session]: : __lttng_complete_session_name all all'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `reclaim-memory` command.
__lttng_complete_reclaim_memory_cmd() {
  specs=(
    $help_opt_specs
    '(-s --session)'{-s+,--session=}'[select a specific recording session]: : __lttng_complete_session_name all all'
    '(-n --no-wait)'{-n,--no-wait}'[exit immediately]'
    '--older-than=[only reclaim sub-buffers older than a threshold]:age (µs; `ms`/`s`/`m`/`h` suffixes supported):'
    '(-u --userspace)'{-u,--userspace}'[select user space tracing domain]'
    + '(channels)'
    {-a,--all}'[reclaim memory for all user space channels]'
    '*: : __lttng_complete_channel_name all opt'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `remove-trigger` command.
__lttng_complete_remove_trigger_cmd() {
  specs=(
    $help_opt_specs
    '--owner-uid=[remove the trigger as another Unix user]: : __lttng_complete_uid'
    ': : __lttng_complete_trigger_name'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `rotate` command.
__lttng_complete_rotate_cmd() {
  specs=(
    $help_opt_specs
    '(-n --no-wait)'{-n,--no-wait}'[exit immediately]'
    ': : __lttng_complete_session_name all all'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `save` command.
__lttng_complete_save_cmd() {
  specs=(
    $help_opt_specs
    '(-f --force)'{-f,--force}'[overwrite existing recording session configuration files]'
    '(-o --output-path)'{-o+,--output-path=}'[save recording session configuration files to a specific directory]:recording session configuration directory:_files -/'
    '(-a --all 1)'{-a,--all}'[save all recording session configurations]'
    '(-a --all): : __lttng_complete_session_name all all'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `set-session` command.
__lttng_complete_set_session_cmd() {
  specs=(
    $help_opt_specs
    ': : __lttng_complete_session_name all all'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `snapshot` command.
__lttng_complete_snapshot_cmd() {
  specs=(
    $help_opt_specs
    '(-): : __lttng_complete_snapshot_action_name' \
    '(-)*:: :->action-args' \
  )

  # Add action name completions
  local curcontext=$curcontext state state_descr line
  local -A opt_args

  if _arguments -C -s -W : $specs; then
    # Completions added: we're done
    return
  fi

  # Add action-specific completions
  local -r common_session_specs=(
    '(-s --session)'{-s+,--session=}'[select a specific recording session]: : __lttng_complete_session_name all snapshot'
  )
  local -r common_output_specs=(
    '(-m --max-size)'{-m+,--max-size=}'[set the maximum total size of all snapshot files]:maximum size (bytes; `k`/`M`/`G` suffixes supported):'
    '(-n --name)'{-n+,--name=}'[set the snapshot output name]:snapshot output name:'
    '(1 -C --ctrl-url output)'{-C+,--ctrl-url=}'[set the control URL]:control URL:'
    '(1 -D --data-url output)'{-D+,--data-url=}'[set the trace data output URL]:trace data output URL:'
    '(-C --ctrl-url -D --data-url): :_guard "^-*" "snapshot output URL"'
  )

  if [[ $state[1] == action-args ]]; then
    # Add completions for the arguments of the specific snapshot action
    curcontext=${curcontext%:*:*}:lttng-snapshot-$line[1]:

    case $line[1] in
      add-output | record)
        specs=($common_session_specs $common_output_specs);;
      del-output)
        specs=($common_session_specs ':snapshot output index:(1)');;
      list-output)
        specs=($common_session_specs);;
      *)
        _message "unknown snapshot action \`$line[1]\`"
        return
        ;;
    esac

    # Add completions
    _arguments -s -W : $specs
    return
  fi

  return 1
}

# Adds completions for the arguments of the `start` command.
__lttng_complete_start_cmd() {
  specs=(
    $help_opt_specs
    + '(session)'
    ': : __lttng_complete_session_name inactive all'
  )

  # The `--glob` and `--all` options require LTTng-tools ≥ 2.14
  if ((minor_version >= 14)); then
    specs+=(
      {-a,--all}'[start all recording sessions]'
      {-g+,--glob=}'[start recording sessions matched by a pattern]:globbing pattern:'
    )
  fi

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `stop` command.
__lttng_complete_stop_cmd() {
  specs=(
    $help_opt_specs
    '(-n --no-wait)'{-n,--no-wait}'[exit immediately]'
    + '(session)'
    ': : __lttng_complete_session_name active all'
  )

  # The `--glob` and `--all` options require LTTng-tools ≥ 2.14
  if ((minor_version >= 14)); then
    specs+=(
      {-a,--all}'[stop all recording sessions]'
      {-g+,--glob=}'[stop recording sessions matched by a pattern]:globbing pattern:'
    )
  fi

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `status` command.
__lttng_complete_status_cmd() {
  specs=(
    $help_opt_specs
  )

  # The forwarded `list` options require LTTng-tools ≥ 2.15
  if ((minor_version >= 15)); then
    specs+=(
      '(-c --channel)'{-c+,--channel=}'[select specific channel(s)]: : __lttng_complete_channel_name all cur'
      '--mem-usage=[set channel memory usage display mode]:memory usage mode:(compact full)'
      '--style=[set command output style]:output style:(compact)'
      '--no-truncate[do not truncate long output lines]'
      '(-k --kernel)'{-k,--kernel}'[select Linux kernel tracing domain]'
      '(-u --userspace)'{-u,--userspace}'[select user space tracing domain]'
      '(-j --jul)'{-j,--jul}'[select `java.util.logging` tracing domain]'
      '(-l --log4j)'{-l,--log4j}'[select Apache log4j 1.x tracing domain]'
      '--log4j2[select Apache Log4j 2 tracing domain]'
      '(-p --python)'{-p,--python}'[select Python tracing domain]'
    )
  fi

  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `track` command.
__lttng_complete_track_cmd() {
  specs=(
    $help_opt_specs
    '(-s --session)'{-s+,--session=}'[select a specific recording session]: : __lttng_complete_session_name all all'
    '(-a --all)'{-a,--all}'[add all possible values to the selected inclusion sets]'
    '(-p --pid)'{-p+,--pid=}'[add values to the process ID inclusion set]:process ID(s):_sequence _pids'
  )

  # Virtual PID and user/group inclusion sets require LTTng-tools ≥ 2.12
  if ((minor_version >= 12)); then
    specs+=(
      '--vpid=[add values to the virtual process ID inclusion set]:virtual process ID(s):_sequence _pids'
      '(-u --userspace)--uid=[add values to the user ID inclusion set]:user(s):_sequence _users'
      '--vuid=[add values to the virtual user ID inclusion set]:virtual user(s):_sequence _users'
      '(-u --userspace)--gid=[add values to the group ID inclusion set]:group(s):_sequence _groups'
      '--vgid=[add values to the virtual group ID inclusion set]:virtual group(s):_sequence _groups'
    )
  fi

  # Append tracing domain specifications
  specs+=(
    + '(domain)'
    {-k,--kernel}'[select the Linux kernel tracing domain]'
    "(--uid --gid)"{-u,--userspace}'[select the user space tracing domain]'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the arguments of the `untrack` command.
__lttng_complete_untrack_cmd() {
  # As of LTTng-tools 2.13, the `track` and `untrack` commands expect
  # the same arguments.
  __lttng_complete_track_cmd
}

# Adds completions for the arguments of the `version` command.
__lttng_complete_version_cmd() {
  _arguments -s -W : $help_opt_specs
}

# Adds completions for the arguments of the `view` command.
__lttng_complete_view_cmd() {
  specs=(
    $help_opt_specs
    '(-e --viewer)'{-e+,--viewer=}'[set the trace reader path]:trace reader path:_files'
    '(-t --trace-path): : __lttng_complete_session_name all all'
    '(1 -t --trace-path)'{-t+,--trace-path=}'[set the trace directory to pass to the reader]:trace directory:_files -/'
  )

  # Add completions
  _arguments -s -W : $specs
}

# Adds completions for the specific `lttng` command named `line[1]`.
__lttng_complete_cmd() {
  # An lttng(1) command: replace `lttng` with `lttng-$line[1]` (for
  # example, `lttng-add-trigger`).
  curcontext=${curcontext%:*:*}:lttng-$line[1]:

  # Shared parameters used by called functions
  local expl
  local -a specs

  # Keep the tracing group: we need to execute `lttng` for some
  # completions and use the required tracing group to connect to the
  # same session daemon.
  #
  # The default tracing group is `tracing`.
  local tracing_group=tracing

  if (($+opt_args[-g])); then
    tracing_group=$opt_args[-g]
  elif (($+opt_args[--group])); then
    tracing_group=$opt_args[--group]
  fi

  # Add command completions: dispatch to a dedicated function
  local -r func_name=__lttng_complete_${line[1]//-/_}_cmd

  if ! typeset -f $func_name >/dev/null; then
    _message "unknown command \`$line[1]\`"
    return
  fi

  local -A opt_args

  $func_name
}

# Ensure predictable Zsh behavior and prevent option leakage
emulate -L zsh
setopt extended_glob

# Make `REPLY` local to this function
local REPLY

# Save program name
local -r prog_name=$words[1]

# First, set the `minor_version` parameter to the minor version of
# LTTng-tools. Some features depend on a specific version and this
# completion function supports many versions from LTTng-tools 2.5.
local -i minor_version

__lttng_set_minor_version

# Exit now with LTTng-tools < 2.5 or LTTng-tools > 2.15
local -r ignore_version_limit=${LTTNG_ZSH_COMP_IGNORE_VERSION_LIMIT:-0}

if ((minor_version < 5 || (minor_version > 15 && !ignore_version_limit))); then
  _message "completion not available for LTTng-tools 2.$minor_version; please update the completion files or set \`LTTNG_ZSH_COMP_IGNORE_VERSION_LIMIT=1\`"
  return
fi

# Common help option specifications
local -r help_opt_specs=(
  '(- : *)'{-h,--help}'[show help]'
)

# Common non Linux kernel tracing domain option exclusions
local -r non_kernel_domain_opt_excl=(-u --userspace -j --jul -l --log4j --log4j2 -p --python)

# General option specifications
local gen_opt_specs=(
  $help_opt_specs
  '(- : *)--list-commands[list the available commands and quit]'
  '--relayd-path=[set the relay daemon path]:relay daemon path:_files -g \*lttng-relayd'
  '--group=[set the tracing group]:tracing group:_groups'
  '(-q --quiet)*'{-v,--verbose}'[increase verbosity]'
  '(-q --quiet -v --verbose)'{-q,--quiet}'[suppress all messages, including warnings and errors]'
  '(- : *)'{-V,--version}'[show version and quit]'
)

# MI output requires LTTng-tools ≥ 2.6
if ((minor_version >= 6)); then
  gen_opt_specs+=(
    '(-m --mi)'{-m+,--mi=}'[use the machine interface output]:machine interface type:(xml)'
  )
fi

# Append session daemon option specifications
gen_opt_specs+=(
  + '(sessiond)'
  {-n,--no-sessiond}'[do not spawn a session daemon]'
  '--sessiond-path=[set the session daemon path]:session daemon path:_files -g \*lttng-sessiond'
)

# Add general option and command name completions
local curcontext=$curcontext state state_descr line
local -A opt_args

_arguments -C -s -W : \
  '(-): : __lttng_complete_cmd_name' \
  '(-)*:: :->cmd-args' \
  $gen_opt_specs

local -ir main_ret=$?

if ((main_ret == 0)); then
  # Completions added: we're done
  return
fi

if [[ $state[1] == cmd-args ]]; then
  # Add completions for the arguments of the specific command
  __lttng_complete_cmd
  return
fi

return $main_ret
