#compdef iw
#

# ZSH completion for iw (Linux wireless configuration utility).
# Parses 'iw help' output to discover commands and caches the result.
#
# $service is used as the iw binary name. This allows this completion to work
# for differently-named iw binaries: compdef _iw my-iw-binary

# Parse 'iw help' output and write a sourceable cache file.
_iw_build_cache() {
  local iw_cmd="$1"
  local version="$2"
  local cache_file="$3"
  local help_output

  help_output=$($iw_cmd help 2>&1) || return 1

  local -a top_cmds dev_subcmds phy_subcmds wdev_subcmds reg_subcmds
  local -A dev_subs phy_subs wdev_subs
  local line
  local -a words lines

  # Single pass: collect top-level commands, subcmds, and sub-subcommands.
  # words[4] entries starting with '<', '[', or '-' are placeholders, not completions.
  lines=(${(f)help_output})
  for line in $lines; do
    words=(${=line})
    [[ $line == $'\t'[a-z]* && $line != $'\t\t'* ]] && top_cmds+=($words[1])
    case $words[1] in
      dev)
        if [[ $words[2] == '<devname>' && $words[3] == [a-z]* ]]; then
          dev_subcmds+=($words[3])
          if (( ${#words} > 3 )); then
            case $words[4] in
              '<'*|'['*|'-'*) ;;
              *) dev_subs[$words[3]]+=" $words[4]" ;;
            esac
          fi
        fi
        ;;
      phy)
        if [[ $words[2] == '<phyname>' && $words[3] == [a-z]* ]]; then
          phy_subcmds+=($words[3])
          if (( ${#words} > 3 )); then
            case $words[4] in
              '<'*|'['*|'-'*) ;;
              *) phy_subs[$words[3]]+=" $words[4]" ;;
            esac
          fi
        fi
        ;;
      wdev)
        if [[ $words[2] == '<idx>' && $words[3] == [a-z]* ]]; then
          wdev_subcmds+=($words[3])
          if (( ${#words} > 3 )); then
            case $words[4] in
              '<'*|'['*|'-'*) ;;
              *) wdev_subs[$words[3]]+=" $words[4]" ;;
            esac
          fi
        fi
        ;;
      reg)
        [[ $words[2] == [a-z]* ]] && reg_subcmds+=($words[2])
        ;;
    esac
  done

  # (ou): sorted + unique, replacing sort -u
  top_cmds=(${(ou)top_cmds})
  dev_subcmds=(${(ou)dev_subcmds})
  phy_subcmds=(${(ou)phy_subcmds})
  wdev_subcmds=(${(ou)wdev_subcmds})
  reg_subcmds=(${(ou)reg_subcmds})

  local subcmd subs_str
  local -a subs_arr tmp_arr

  {
    print "# iw completion cache"
    print "# version: ${version}"

    print "_iw_top_cmds=(${top_cmds[*]})"
    print "_iw_dev_subcmds=(${dev_subcmds[*]})"
    for subcmd in $dev_subcmds; do
      if [[ -n "${dev_subs[$subcmd]}" ]]; then
        tmp_arr=(${=dev_subs[$subcmd]})
        subs_arr=(${(ou)tmp_arr})
        subs_str="${(j: :)subs_arr}"
        print "_iw_dev_subsubcmds[${subcmd}]=${(qq)subs_str}"
      fi
    done

    print "_iw_phy_subcmds=(${phy_subcmds[*]})"
    for subcmd in $phy_subcmds; do
      if [[ -n "${phy_subs[$subcmd]}" ]]; then
        tmp_arr=(${=phy_subs[$subcmd]})
        subs_arr=(${(ou)tmp_arr})
        subs_str="${(j: :)subs_arr}"
        print "_iw_phy_subsubcmds[${subcmd}]=${(qq)subs_str}"
      fi
    done

    print "_iw_wdev_subcmds=(${wdev_subcmds[*]})"
    for subcmd in $wdev_subcmds; do
      if [[ -n "${wdev_subs[$subcmd]}" ]]; then
        tmp_arr=(${=wdev_subs[$subcmd]})
        subs_arr=(${(ou)tmp_arr})
        subs_str="${(j: :)subs_arr}"
        print "_iw_wdev_subsubcmds[${subcmd}]=${(qq)subs_str}"
      fi
    done

    print "_iw_reg_subcmds=(${reg_subcmds[*]})"

  } >| "$cache_file"
}

_iw() {
  local context state line curcontext="$curcontext"
  integer ret=1

  # Declare cache variables; populated by sourcing the cache file below.
  local -a _iw_top_cmds _iw_dev_subcmds _iw_phy_subcmds _iw_wdev_subcmds _iw_reg_subcmds
  local -A _iw_dev_subsubcmds _iw_phy_subsubcmds _iw_wdev_subsubcmds

  local cache_file="${ZSH_CACHE_DIR:-${HOME}/.cache}/_iw_cache"
  local current_version ver_output
  ver_output=$(${service} --version 2>&1)
  [[ $ver_output =~ '([0-9]+\.[0-9]+)' ]] && current_version=$match[1] || current_version=""

  local cache_ver="" _l1 _l2
  if [[ -f "$cache_file" ]]; then
    { IFS= read -r _l1; IFS= read -r _l2; } < "$cache_file" 2>/dev/null
    cache_ver=${_l2#\# version: }
  fi

  if [[ $cache_ver != $current_version ]]; then
    _iw_build_cache "${service}" "$current_version" "$cache_file"
  fi

  source "$cache_file" 2>/dev/null

  _arguments -C \
    '--debug[enable netlink debugging]' \
    '(- 1 2 3 4)--version[show version]' \
    '1:: :->cmd' \
    '2:: :->arg2' \
    '3:: :->arg3' \
    '4:: :->arg4' \
    && return 0

  case "$state" in
    cmd)
      local -A _iw_cmd_descs
      _iw_cmd_descs=(
        dev      'network interface commands'
        phy      'wireless hardware commands'
        wdev     'wireless device commands'
        reg      'regulatory domain commands'
        list     'list all wireless devices and their capabilities'
        event    'monitor kernel events'
        features 'list supported features'
        commands 'list all known commands'
        help     'show command usage'
      )
      local -a top_cmds
      local _cmd
      for _cmd in $_iw_top_cmds; do
        if [[ -n "${_iw_cmd_descs[$_cmd]}" ]]; then
          top_cmds+=("${_cmd}:${_iw_cmd_descs[$_cmd]}")
        else
          top_cmds+=("$_cmd")
        fi
      done
      _describe -t commands 'iw command' top_cmds && ret=0
      ;;

    arg2)
      case "$line[1]" in
        dev)
          local -a wlan_devs
          wlan_devs=(/sys/class/net/*/phy80211(N:h:t))
          compadd -a wlan_devs && ret=0
          ;;
        phy)
          local -a phy_devs
          phy_devs=(/sys/class/ieee80211/*(N:t))
          compadd -a phy_devs && ret=0
          ;;
        reg)
          compadd -a _iw_reg_subcmds && ret=0
          ;;
      esac
      ;;

    arg3)
      case "$line[1]" in
        dev)  compadd -a _iw_dev_subcmds  && ret=0 ;;
        phy)  compadd -a _iw_phy_subcmds  && ret=0 ;;
        wdev) compadd -a _iw_wdev_subcmds && ret=0 ;;
      esac
      ;;

    arg4)
      local -a subs
      case "$line[1]" in
        dev)  subs=(${=_iw_dev_subsubcmds[$line[3]]})  ;;
        phy)  subs=(${=_iw_phy_subsubcmds[$line[3]]})  ;;
        wdev) subs=(${=_iw_wdev_subsubcmds[$line[3]]}) ;;
      esac
      (( ${#subs} > 0 )) && compadd -a subs && ret=0
      ;;
  esac

  return ret
}

_iw "$@"
