#!/usr/bin/env bash

set -u

version=0.73.1
auto_completion=
key_bindings=
update_config=2
shells="bash zsh fish nushell"
prefix='~/.fzf'
prefix_expand=~/.fzf
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish

help() {
  cat << EOF
usage: $0 [OPTIONS]

    --help               Show this message
    --bin                Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
    --all                Download fzf binary and update configuration files
                         to enable key bindings and fuzzy completion
    --xdg                Generate files under \$XDG_CONFIG_HOME/fzf
    --[no-]key-bindings  Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
    --[no-]completion    Enable/disable fuzzy completion (bash & zsh)
    --[no-]update-rc     Whether or not to update shell configuration files

    --no-bash            Do not set up bash configuration
    --no-zsh             Do not set up zsh configuration
    --no-fish            Do not set up fish configuration
    --no-nushell         Do not set up nushell configuration
EOF
}

for opt in "$@"; do
  case $opt in
    --help)
      help
      exit 0
      ;;
    --all)
      auto_completion=1
      key_bindings=1
      update_config=1
      ;;
    --xdg)
      prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
      prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf
      mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/fzf"
      ;;
    --key-bindings) key_bindings=1 ;;
    --no-key-bindings) key_bindings=0 ;;
    --completion) auto_completion=1 ;;
    --no-completion) auto_completion=0 ;;
    --update-rc) update_config=1 ;;
    --no-update-rc) update_config=0 ;;
    --bin) ;;
    --no-bash) shells=${shells/bash/} ;;
    --no-zsh) shells=${shells/zsh/} ;;
    --no-fish) shells=${shells/fish/} ;;
    --no-nushell) shells=${shells/nushell/} ;;
    *)
      echo "unknown option: $opt"
      help
      exit 1
      ;;
  esac
done

cd "$(dirname "${BASH_SOURCE[0]}")"
fzf_base=$(pwd)
fzf_base_esc=$(printf %q "$fzf_base")

ask() {
  while true; do
    read -p "$1 ([y]/n) " -r
    REPLY=${REPLY:-"y"}
    if [[ $REPLY =~ ^[Yy]$ ]]; then
      return 1
    elif [[ $REPLY =~ ^[Nn]$ ]]; then
      return 0
    fi
  done
}

check_binary() {
  echo -n "  - Checking fzf executable ... "
  local output
  output=$(FZF_DEFAULT_OPTS= "$fzf_base"/bin/fzf --version 2>&1)
  if [ $? -ne 0 ]; then
    echo "Error: $output"
    binary_error="Invalid binary"
  else
    output=${output/ */}
    if [ "$version" != "$output" ]; then
      echo "$output != $version"
      binary_error="Invalid version"
    else
      echo "$output"
      binary_error=""
      return 0
    fi
  fi
  rm -f "$fzf_base"/bin/fzf
  return 1
}

link_fzf_in_path() {
  if which_fzf="$(command -v fzf)"; then
    echo '  - Found in $PATH'
    echo "  - Creating symlink: bin/fzf -> $which_fzf"
    (cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
    check_binary && return
  fi
  return 1
}

tar_opts="-xzf -"
if tar --no-same-owner -tf /dev/null 2> /dev/null; then
  tar_opts="--no-same-owner $tar_opts"
fi

try_curl() {
  command -v curl > /dev/null &&
    if [[ $1 =~ tar.gz$ ]]; then
      curl -fL $1 | tar $tar_opts
    else
      local temp=${TMPDIR:-/tmp}/fzf.zip
      curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
    fi
}

try_wget() {
  command -v wget > /dev/null &&
    if [[ $1 =~ tar.gz$ ]]; then
      wget -O - $1 | tar $tar_opts
    else
      local temp=${TMPDIR:-/tmp}/fzf.zip
      wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
    fi
}

download() {
  echo "Downloading bin/fzf ..."
  if [ -x "$fzf_base"/bin/fzf ]; then
    echo "  - Already exists"
    check_binary && return
  fi
  link_fzf_in_path && return
  mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin
  if [ $? -ne 0 ]; then
    binary_error="Failed to create bin directory"
    return
  fi

  local url
  url=https://github.com/junegunn/fzf/releases/download/v$version/${1}
  set -o pipefail
  if ! (try_curl $url || try_wget $url); then
    set +o pipefail
    binary_error="Failed to download with curl and wget"
    return
  fi
  set +o pipefail

  if [ ! -f fzf ]; then
    binary_error="Failed to download ${1}"
    return
  fi

  chmod +x fzf && check_binary
}

# Try to download binary executable
archi=$(uname -smo 2> /dev/null || uname -sm)
binary_available=1
binary_error=""
case "$archi" in
  Darwin\ arm64*) download fzf-$version-darwin_arm64.tar.gz ;;
  Darwin\ x86_64*) download fzf-$version-darwin_amd64.tar.gz ;;
  Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
  Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
  Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
  Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
  Linux\ aarch64\ Android) download fzf-$version-android_arm64.tar.gz ;;
  Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
  Linux\ loongarch64*) download fzf-$version-linux_loong64.tar.gz ;;
  Linux\ riscv64*) download fzf-$version-linux_riscv64.tar.gz ;;
  Linux\ ppc64le*) download fzf-$version-linux_ppc64le.tar.gz ;;
  Linux\ *64*) download fzf-$version-linux_amd64.tar.gz ;;
  Linux\ s390x*) download fzf-$version-linux_s390x.tar.gz ;;
  FreeBSD\ *64*) download fzf-$version-freebsd_amd64.tar.gz ;;
  OpenBSD\ *64*) download fzf-$version-openbsd_amd64.tar.gz ;;
  CYGWIN*\ *64*) download fzf-$version-windows_amd64.zip ;;
  MINGW*\ *64*) download fzf-$version-windows_amd64.zip ;;
  MSYS*\ *64*) download fzf-$version-windows_amd64.zip ;;
  Windows*\ *64*) download fzf-$version-windows_amd64.zip ;;
  *) binary_available=0 binary_error=1 ;;
esac

cd "$fzf_base"
if [ -n "$binary_error" ]; then
  if [ $binary_available -eq 0 ]; then
    echo "No prebuilt binary for $archi ..."
  else
    echo "  - $binary_error !!!"
  fi
  if command -v go > /dev/null; then
    echo -n "Building binary (go install github.com/junegunn/fzf) ... "
    if [ -z "${GOPATH-}" ]; then
      export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
      mkdir -p "$GOPATH"
    fi
    if go install -ldflags "-s -w -X main.version=$version -X main.revision=go-install" github.com/junegunn/fzf; then
      echo "OK"
      cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
    else
      echo "Failed to build binary. Installation failed."
      exit 1
    fi
  else
    echo "go executable not found. Installation failed."
    exit 1
  fi
fi

[[ $* =~ "--bin" ]] && exit 0

for s in $shells; do
  bin=$s
  [[ "$s" = nushell ]] && bin=nu
  if ! command -v "$bin" > /dev/null; then
    shells=${shells/$s/}
  fi
done

if [[ ${#shells} -lt 3 ]]; then
  echo "No shell configuration to be updated."
  exit 0
fi

# Auto-completion
if [ -z "$auto_completion" ]; then
  ask "Do you want to enable fuzzy auto-completion?"
  auto_completion=$?
fi

# Key-bindings
if [ -z "$key_bindings" ]; then
  ask "Do you want to enable key bindings?"
  key_bindings=$?
fi

echo
for shell in $shells; do
  fzf_completion="source \"$fzf_base/shell/completion.${shell}\""
  fzf_key_bindings="source \"$fzf_base/shell/key-bindings.${shell}\""
  [[ $shell == fish ]] && continue
  [[ $shell == nushell ]] && continue
  src=${prefix_expand}.${shell}
  echo -n "Generate $src ... "

  if [ $auto_completion -eq 0 ]; then
    fzf_completion="# $fzf_completion"
  fi

  if [ $key_bindings -eq 0 ]; then
    fzf_key_bindings="# $fzf_key_bindings"
  fi

  cat > "$src" << EOF
# Setup fzf
# ---------
if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
  PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
fi

EOF

  if [[ $auto_completion -eq 1 ]] && [[ $key_bindings -eq 1 ]]; then
    if [[ $shell == zsh ]]; then
      echo "source <(fzf --$shell)" >> "$src"
    else
      echo "eval \"\$(fzf --$shell)\"" >> "$src"
    fi
  else
    cat >> "$src" << EOF
# Auto-completion
# ---------------
$fzf_completion

# Key bindings
# ------------
$fzf_key_bindings
EOF
  fi
  echo "OK"
done

# fish
if [[ $shells =~ fish ]]; then
  echo -n "Update fish_user_paths ... "
  fish << EOF
  echo \$fish_user_paths | \grep "$fzf_base"/bin > /dev/null
  or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin
EOF
  [ $? -eq 0 ] && echo "OK" || echo "Failed"
fi

append_line() {
  local update line file pat lines
  update="$1"
  line="$2"
  file="$3"
  pat="${4:-}"
  at_lno="${5:-}"
  lines=""

  echo "Update $file:"
  echo "  - $line"
  if [ -f "$file" ]; then
    if [[ -n $pat ]]; then
      lines=$(\grep -nF "$pat" "$file")
    else
      lines=$(\grep -nF "${line#"${line%%[![:space:]]*}"}" "$file")
    fi
  fi

  if [ -n "$lines" ]; then
    echo "    - Already exists:"
    sed 's/^/        Line /' <<< "$lines"

    update=0
    if ! \grep -qv "^[0-9]*:[[:space:]]*#" <<< "$lines"; then
      echo "    - But they all seem to be commented"
      ask "    - Continue modifying $file?"
      update=$?
    fi
  fi

  set -e
  if [ "$update" -eq 1 ]; then
    if [[ -z $at_lno ]]; then
      [ -f "$file" ] && echo >> "$file"
      echo "$line" >> "$file"
    else
      sed -i.~fzf_bak "${at_lno}a\\"$'\n'"$line" "$file" && rm "$file.~fzf_bak"
    fi
    echo "    + Added"
  else
    echo "    ~ Skipped"
  fi

  echo
  set +e
}

create_file() {
  local file="$1"
  shift
  echo "Create $file:"
  for line in "$@"; do
    echo "    $line"
    echo "$line" >> "$file"
  done
  echo
}

if [ $update_config -eq 2 ]; then
  echo
  ask "Do you want to update your shell configuration files?"
  update_config=$?
fi
echo
for shell in $shells; do
  [[ $shell == fish ]] && continue
  [[ $shell == nushell ]] && continue
  [ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
  append_line $update_config "[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" "$dest" "${prefix}.${shell}"
done

if [[ $shells =~ fish ]]; then
  bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
  if [ ! -e "$bind_file" ]; then
    if [[ $key_bindings -eq 1 || $auto_completion -eq 1 ]]; then
      mkdir -p "${fish_dir}/functions"
      if [[ $key_bindings -eq 1 && $auto_completion -eq 1 ]]; then
        create_file "$bind_file" \
          'function fish_user_key_bindings' \
          '  fzf --fish | source' \
          'end'
      elif [[ $key_bindings -eq 1 ]]; then
        create_file "$bind_file" \
          'function fish_user_key_bindings' \
          "  $fzf_key_bindings" \
          'end'
      elif [[ $auto_completion -eq 1 ]]; then
        create_file "$bind_file" \
          'function fish_user_key_bindings' \
          "  $fzf_completion" \
          'end'
      fi
      lno_func=$(\grep -nF "function fish_user_key_bindings" "$bind_file" | sed 's/:.*//' | tr '\n' ' ')
    else
      lno_func=0
    fi
  else
    echo "Check $bind_file:"
    lno_func=$(\grep -nF "function fish_user_key_bindings" "$bind_file" | sed 's/:.*//' | tr '\n' ' ')
    if [[ -z $lno_func ]]; then
      echo -e "function fish_user_key_bindings\nend" >> "$bind_file"
      lno_func=$(\grep -nF "function fish_user_key_bindings" "$bind_file" | sed 's/:.*//' | tr '\n' ' ')
    fi
    lno_keys=$(\grep -nF "fzf_key_bindings" "$bind_file" | sed 's/:.*//' | tr '\n' ' ')
    if [[ -n $lno_keys ]]; then
      echo "  ** Found 'fzf_key_bindings' in line #$lno_keys"
      if [[ $key_bindings -eq 1 && $auto_completion -eq 1 ]]; then
        echo "  ** You have to replace the line to 'fzf --fish | source'"
      elif [[ $key_bindings -eq 1 ]]; then
        echo "  ** You have to replace the line to '$fzf_key_bindings'"
      else
        echo "  ** You have to remove the line"
      fi
      echo
    else
      echo "  - Clear"
      echo
      if [[ $key_bindings -eq 1 && $auto_completion -eq 1 ]]; then
        sed -i.~fzf_bak "\#$fzf_completion#d" "$bind_file" && rm "$bind_file.~fzf_bak"
        sed -i.~fzf_bak "\#$fzf_key_bindings#d" "$bind_file" && rm "$bind_file.~fzf_bak"
        append_line $update_config "  fzf --fish | source" "$bind_file" "" "$lno_func"
      else
        sed -i.~fzf_bak '/fzf --fish \| source/d' "$bind_file" && rm "$bind_file.~fzf_bak"
        if [[ $key_bindings -eq 1 ]]; then
          sed -i.~fzf_bak "\#$fzf_completion#d" "$bind_file" && rm "$bind_file.~fzf_bak"
          append_line $update_config "  $fzf_key_bindings" "$bind_file" "" "$lno_func"
        elif [[ $auto_completion -eq 1 ]]; then
          sed -i.~fzf_bak "\#$fzf_key_bindings#d" "$bind_file" && rm "$bind_file.~fzf_bak"
          append_line $update_config "  $fzf_completion" "$bind_file" "" "$lno_func"
        fi
      fi
    fi
  fi
fi

if [[ "$shells" =~ nushell ]]; then
  if [[ $key_bindings -eq 1 || $auto_completion -eq 1 ]]; then
    echo "Setting up Nushell integration ..."
    nushell_autoload_dir=$(nu -c '$nu.user-autoload-dirs | first')
    mkdir -p "$nushell_autoload_dir"
    dest="$nushell_autoload_dir/_fzf_integration.nu"
    echo -n "  Generate $dest ... "
    if [[ $key_bindings -eq 1 && $auto_completion -eq 1 ]]; then
      "$fzf_base"/bin/fzf --nushell > "$dest"
    elif [[ $key_bindings -eq 1 ]]; then
      cp "$fzf_base/shell/key-bindings.nu" "$dest"
    else
      cp "$fzf_base/shell/completion.nu" "$dest"
    fi
    echo "OK"
    echo
  fi
fi

if [ $update_config -eq 1 ]; then
  echo 'Finished. Restart your shell or reload config file.'
  if [[ $shells =~ bash ]]; then
    echo -n '   source ~/.bashrc  # bash'
    [[ $archi =~ Darwin ]] && echo -n '  (.bashrc should be loaded from .bash_profile)'
    echo
  fi
  [[ $shells =~ zsh ]] && echo "   source ${ZDOTDIR:-~}/.zshrc   # zsh"
  [[ $shells =~ fish && $lno_func -ne 0 ]] && echo '   fish_user_key_bindings  # fish'
  [[ $shells =~ nushell ]] && echo '   # nushell: files are loaded automatically from autoload directory'
  echo
  echo 'Use uninstall script to remove fzf.'
  echo
fi
echo 'For more information, see: https://github.com/junegunn/fzf'
