## zsh_directory_name_generic
#
# This function is useful as a hook function for the zsh_directory_name
# facility.
#
# See the zsh-contrib manual page for more.

emulate -L zsh
setopt extendedglob
local -a match mbegin mend

# The variable containing the top level mapping.
local _zdn_topvar

zmodload -i zsh/parameter
zstyle -s ":zdn:${funcstack[2]}:" mapping _zdn_topvar || _zdn_topvar=zdn_top

if (( ! ${(P)#_zdn_topvar} )); then
  print -r -- "$0: $_zdn_topvar is not set" >&2
  return 1
fi

local _zdn_var=$_zdn_topvar
local -A _zdn_assoc

if [[ $1 = n ]]; then
  # Turning a name into a directory.
  local _zdn_name=$2
  local -a _zdn_words
  local _zdn_dir _zdn_cpt

  _zdn_words=(${(s.:.)_zdn_name})
  while (( ${#_zdn_words} )); do
    if [[ -z ${_zdn_var} ]]; then
      print -r -- "$0: too many components in directory name \`$_zdn_name'" >&2
      return 1
    fi

    # Subscripting (P)_zdn_var directly seems not to work.
    _zdn_assoc=(${(Pkv)_zdn_var})
    _zdn_cpt=${_zdn_assoc[${_zdn_words[1]}]}
    shift _zdn_words

    if [[ -z $_zdn_cpt ]]; then
      # If top level component, just try another expansion
      if [[ $_zdn_var != $_zdn_topvar ]]; then
	# Committed to this expansion, so report failure.
	print -r -- "$0: no expansion for directory name \`$_zdn_name'" >&2
      fi
      return 1
    fi
    if [[ $_zdn_cpt = (#b)(*)/:([[:IDENT:]]##) ]]; then
      _zdn_cpt=$match[1]
      _zdn_var=$match[2]
    else
      # may be empty
      _zdn_var=${${_zdn_assoc[:default:]}##*/:}
    fi
    _zdn_dir=${_zdn_dir:+$_zdn_dir/}$_zdn_cpt
  done
  if (( ${#_zdn_dir} )); then
    typeset -ag reply
    reply=($_zdn_dir)
    return 0
  fi
elif [[ $1 = d ]]; then
  # Turning a directory into a name.
  local _zdn_dir=$2
  local _zdn_rest=$_zdn_dir
  local -a _zdn_cpts
  local _zdn_pref _zdn_pref_raw _zdn_matched _zdn_cpt _zdn_name

  while [[ -n $_zdn_var && -n $_zdn_rest ]]; do
    _zdn_assoc=(${(Pkv)_zdn_var})
    # Sorting in descending order will ensure prefixes
    # come after longer strings with that perfix, so
    # we match more specific directory names preferentially.
    _zdn_cpts=(${(Ov)_zdn_assoc})
    _zdn_cpt=''
    for _zdn_pref_raw in $_zdn_cpts; do
      _zdn_pref=${_zdn_pref_raw%/:*}
      [[ -z $_zdn_pref ]] && continue
      if [[ $_zdn_rest = $_zdn_pref(#b)(/|)(*) ]]; then
	_zdn_cpt=${(k)_zdn_assoc[(r)$_zdn_pref_raw]}
	# if we matched a /, too, add it...
	_zdn_matched+=$_zdn_pref$match[1]
	_zdn_rest=$match[2]
	break
      fi
    done
    if [[ -n $_zdn_cpt ]]; then
      _zdn_name+=${_zdn_name:+${_zdh_name}:}$_zdn_cpt
      if [[ ${_zdn_assoc[$_zdn_cpt]} = (#b)*/:([[:IDENT:]]##) ]]; then
	_zdn_var=$match[1]
      else
	_zdn_var=${${_zdn_assoc[:default:]}##*/:}
      fi
    else
      break
    fi
  done
  if [[ -n $_zdn_name ]]; then
    # matched something, so report that.
    integer _zdn_len=${#_zdn_matched}
    [[ $_zdn_matched[-1] = / ]] && (( _zdn_len-- ))
    typeset -ag reply
    reply=($_zdn_name $_zdn_len)
    return 0
  fi
  # else let someone else have a go.
elif [[ $1 = c ]]; then
  # Completion

  if [[ -n $SUFFIX ]]; then
    _message "Can't complete in the middle of a dynamic directory name"
  else
    local -a _zdn_cpts
    local _zdn_word _zdn_cpt _zdn_desc _zdn_sofar expl

    while [[ -n ${_zdn_var} && ${PREFIX} = (#b)([^:]##):* ]]; do
      _zdn_word=$match[1]
      compset -P '[^:]##:'
      _zdn_assoc=(${(Pkv)_zdn_var})
      _zdn_cpt=${_zdn_assoc[$_zdn_word]}
      # We only complete at the end so must match here
      [[ -z $_zdn_cpt ]] && return 1
      if [[ $_zdn_cpt = (#b)(*)/:([[:IDENT:]]##) ]]; then
	_zdn_cpt=$match[1]
	_zdn_var=$match[2]
      else
	_zdn_var=${${_zdn_assoc[:default:]}##*/:}
      fi
      _zdn_sofar+=${_zdn_sofar:+${_zdn_sofar}/}$_zdn_cpt
    done
    if [[ -n $_zdn_var ]]; then
      _zdn_assoc=(${(Pkv)_zdn_var})
      local -a _zdn_cpts
      for _zdn_cpt _zdn_desc in ${(kv)_zdn_assoc}; do
	[[ $_zdn_cpt = :* ]] && continue
	_zdn_cpts+=(${_zdn_cpt}:${_zdn_desc%/:[[:IDENT:]]##})
      done
      _describe -t dirnames "directory name under ${_zdn_sofar%%/}" \
	_zdn_cpts -S: -r ':]'
      return
    fi
  fi
fi

# Failed
return 1
## end
