#!/bin/zsh
zmodload -Fa zsh/files b:zf_rm
zmodload -F zsh/parameter p:funcstack p:functions
builtin autoload -Uz is-at-least

typeset -ga _autocomplete__compdef=()
compdef() {
  typeset -ga _autocomplete__compdef=( $_autocomplete__compdef[@] "${(j: :)${(@q+)@}}" )
}

[[ -v functions[_bash_complete] ]] ||
    _bash_complete compgen complete () {
      unfunction _bash_complete compgen complete
      builtin autoload +X -Uz bashcompinit
      bashcompinit
      bashcompinit() { : }
      ${(%):-%N} "$@"
    }

${0}:precmd() {
  emulate -L zsh
  setopt $_autocomplete__func_opts[@]

  [[ -v CDPATH && -z $CDPATH ]] &&
      unset CDPATH cdpath

  # Decrease Oh My Zsh start-up time. See below.
  local -Pa omzdump=()
  [[ -v ZSH_COMPDUMP && -r $ZSH_COMPDUMP ]] &&
      omzdump=( ${(f)"$( < $ZSH_COMPDUMP )"} )

  typeset -g \
      _comp_dumpfile=${_comp_dumpfile:-${ZSH_COMPDUMP:-${XDG_CACHE_HOME:-$HOME/.cache}/zsh/compdump}}

  if [[ -v _comps[-command-] && $_comps[-command-] != _autocomplete__command ]]; then
    zf_rm -f $_comp_dumpfile
  else

    # Check if our most recently modified completion function is newer than the comp dump file.
    local -Pa newest=( ~autocomplete/Completions/_*~*.zwc(N-.omY1) )
    if [[ $newest[1] -nt $_comp_dumpfile ]]; then
      zf_rm -f $_comp_dumpfile
    fi
  fi

  if [[ ! -v _comp_setup ]] || [[ ! -r $_comp_dumpfile ]]; then
    unfunction compdef compinit 2> /dev/null
    bindkey() { : }
    {
      builtin autoload +X -Uz compinit
      local -a compargs=()
      zstyle -a ':autocomplete::compinit' arguments compargs
      compinit -d "$_comp_dumpfile" "$compargs[@]"
    } always {
      unfunction bindkey
    }
    bindkey '^Xh' _complete_help

    # Prevent Oh My Zsh from deleting comp dump file.
    (( ${#omzdump[@]} > 0 )) &&
      tee -a "$ZSH_COMPDUMP" &> /dev/null <<EOF
$omzdump[-2]
$omzdump[-1]
EOF
  fi

  compinit() { : }

  local -P args=
  for args in "$_autocomplete__compdef[@]"; do
    eval "compdef $args"
  done
  unset _autocomplete__compdef

  (
    local -a reply=()
    local cache_dir=
    if builtin zstyle -s ':completion:*' cache-path cache_dir; then
      local -P src= bin=
      for src in $cache_dir/*~**.zwc~**/.*(N-.); do
        bin=$src.zwc
        if [[ ! -e $bin || $bin -ot $src ]]; then
          zcompile -Uz $src
        fi
      done
    fi
  ) &|

  # Workaround: Some other plugins rely on patching _main_complete, which can interfere with our completion.
  .autocomplete__patch _main_complete
  autocomplete:_main_complete:new() {
    local -i _autocomplete__reserved_lines=0
    local -Pi ret=1
    unset _autocomplete__partial_list _autocomplete__unambiguous

    compstate[last_prompt]=yes  # Completion doesn't stay on the same command line without this.
    compstate[list]='list force packed rows' # `_setup` doesn't work well for this.
    unset 'compstate[vared]'

    local +h -a comppostfuncs=( autocomplete:_main_complete:new:post "$comppostfuncs[@]" )
    autocomplete:_main_complete:old "$@"
  }

  autocomplete:_main_complete:new:post() {
    [[ $WIDGET != _complete_help ]] &&
        unfunction compadd 2> /dev/null
    _autocomplete__unambiguous
    compstate[list_max]=0
    MENUSCROLL=0
  }

  _expand() {
    local word=$words[CURRENT]
    [[ $word == (|*[^\\])\`?*\`* || $word == (|*[^\\])'$('*')'* ]] &&
        return 1

    local special=${(j:|:)${(kb)parameters[(R)*-special]}}
    [[
        $word == *'$'($~special)(|[^[:alnum:]]*) ||
        $word == *'$'($~special)(:[/:[:alnum:]]*)#(|[^[:alnum:]]*) ||
        $word == *'${'(|\(?*\))[^_[:alnum:]]#($~special)(:?*)#'}'*
    ]] &&
        return 1

    local expansion

    expansion="${(b)$( eval print -r -- $word )}" 2> /dev/null ||
        return 1
    [[ $expansion == $word ]] &&
        return 1

    local -a expl
    _description expansions expl expansion
    compadd -QU "$expl[@]" -- $expansion
    return 1
  }

  .autocomplete__patch _complete
  _complete() {
    local -i nmatches=$compstate[nmatches]

    PREFIX=$PREFIX$SUFFIX
    SUFFIX=
    autocomplete:_complete:old "$@"

    # WORKAROUND: Some completion functions mistakenly don't return 0 when they have succeeded.
    (( compstate[nmatches] > nmatches ))
  }


  ##
  # WORKAROUND: _approximate won't do corrections if there already is a function called 'compadd'.
  #

  .autocomplete__patch _approximate
  _approximate() {
    {
      [[ -v functions[compadd] ]] &&
          functions[autocomplete:compadd:old]="$functions[compadd]"
      functions[compadd]="$functions[autocomplete:approximate:compadd]"

      autocomplete:_approximate:old
    } always {
      unfunction compadd 2> /dev/null
      if [[ -v functions[autocomplete:compadd:old] ]]; then
        functions[compadd]="$functions[autocomplete:compadd:old]"
        unfunction autocomplete:compadd:old
      fi
    }
  }

  autocomplete:approximate:compadd() {
    local ppre="$argv[(I)-p]"

    [[ ${argv[(I)-[a-zA-Z]#U[a-zA-Z]#]} -eq 0 &&
       "${#:-$PREFIX$SUFFIX}" -le _comp_correct ]] && return

    if [[ "$PREFIX" = \~* && ( ppre -eq 0 || "$argv[ppre+1]" != \~* ) ]]; then
      PREFIX="~(#a${_comp_correct})${PREFIX[2,-1]}"
    else
      PREFIX="(#a${_comp_correct})$PREFIX"
    fi

    if [[ -v functions[autocomplete:compadd:old] ]]; then
      autocomplete:compadd:old "$@"
    else
      builtin compadd "$@"
    fi
  }
}
