#!/usr/bin/env sh
# rofipass - rofi front end for pass
#
# Derived from rofipass by Augusto Coronel (https://github.com/aocoronel/rofipass),
# released under the MIT License, whose notice is kept below as it requires.
# Modifications Copyright (C) 2026 Danilo M. <danix@danix.xyz>, released under
# the GNU General Public License version 2 (see LICENSE).
#
# MIT License
#
# Copyright (c) 2025 Augusto Coronel
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

### Colors

help_color="#7c5cff"
div_color="#334433"
label="#f067fc"

###

### Bindings

kb_copy_email="Ctrl+2" # EMAIL
kb_copy_otp="Ctrl-1"   # COPY TOTP
kb_add_pass="Ctrl+r"   # PASSWORD
kb_add_otp="Ctrl+t"    # ADD TOTP
kb_delete="Ctrl-x"     # DELETE
kb_edit="Ctrl+y"       # EDIT
kb_close_tomb="Ctrl-z" # CLOSE TOMB
kb_open_tomb="Ctrl-o"  # OPEN TOMB

###

clipmethod="$XDG_SESSION_TYPE"
passdir=${PASSWORD_STORE_DIR:-$HOME/.password-store}

COLOR_YELLOW="$(tput setaf 226)"
COLOR_RED="$(tput setaf 196)"
COLOR_GREEN="$(tput setaf 82)"
COLOR_BLUE="$(tput setaf 87)"
COLOR_RESET=$(tput sgr0)

printfc() {
  mode=$1
  case $mode in
  "ERROR" | "FATAL")
    color="$COLOR_RED"
    ;;
  "WARN")
    mode="WARNING"
    color="$COLOR_YELLOW"
    ;;
  "DEBUG")
    color="$COLOR_BLUE"
    ;;
  "INFO")
    color="$COLOR_GREEN"
    ;;
  esac
  text=$2
  printf "%s[%s]:%s %s\n" "${color}" "${mode}" "${COLOR_RESET}" "${text}"
}

check_command() {
  for cmd in "$@"; do
    if ! command -v "$cmd" >/dev/null 2>&1; then
      printfc FATAL "$cmd is not installed."
      return 1
    fi
  done
}

_notify() {
  message="$1"
  header="rofipass"
  urgency="$2"
  time=${3:-$time}
  echo "$message"
  if [ "$time" -eq 0 ]; then
    notify-send -u "$urgency" "$header" "$message"
  else
    notify-send -u "$urgency" "$header" "$message" --expire-time="$time"000 --wait
  fi
}

die() {
  mode=$1
  message=$2
  urgency=$3

  printfc "$mode" "$message"
  _notify "$message" "$urgency"
  exit 1
}

version() {
  echo "v1.1.0"
}

usage() {
  cat <<EOF
rofipass | A rofi extension for pass

Usage: rofipass [OPTIONS] <COMMAND>

Options:
  -e <EDITOR>    Set editor
  -f             Lift swap restrictions (for tomb)
  -h             Display this help message and exit
  -l <LENGTH>    Default password length on generation
  -t <TERM>      Default terminal emulator (for tomb)
  -v             Display the current version number
  -T <SECONDS>   Clearing time

Example:
  rofipass -f -l 72 -t kitty -e nvim -T 10
EOF
}

_rofi() {
  rofi -dmenu -theme ~/.config/rofi/udt/list.rasi -i -no-levenshtein-sort "$@"
}

copy_email() {
  email=$(pass "$menu" | sed -n 's/^email=//p')
  case "$clipmethod" in
  "x11")
    xclip "$email" || die FATAL "failed to copy the email." "critical"
    _notify "Copied to clipboard. Clearing in $time seconds" "normal"
    ;;
  "wayland")
    wl-copy "$email" || die FATAL "failed to copy the email." "critical"
    _notify "Copied to clipboard. Clearing in $time seconds" "normal"
    ;;
  esac
  # clearboard
}

append_otp() {
  check_command zbarimg || die FATAL "zbarimg is not installed." "critical"
  case "$clipmethod" in
  "x11")
    xclip -o | zbarimg -q --raw - | pass otp append "$menu" || die FATAL "failed to append OTP to $menu" "critical"
    _notify "Inserted OTP successfully to $menu" "normal"
    ;;
  "wayland")
    wl-paste --type image/png | zbarimg -q --raw - | pass otp append "$menu" || die FATAL "failed to append OTP to $menu" "critical"
    _notify "Inserted OTP successfully to $menu" "normal"
    ;;
  esac
}

clearboard() {
  [ -x "$(command -v notifyctl)" ] && notifyctl close-all

  case "$clipmethod" in
  "x11")
    echo "" | xclip -sel clip
    ;;
  "wayland")
    [ -x "$(command -v cliphist)" ] && cliphist wipe
    echo "" | wl-copy
    ;;
  esac
}

tomb_open() {
  if [ -n "$FORCE" ]; then
    $term -e sh -c "pass open -f"
  else
    $term -e sh -c "pass open"
  fi
}

tomb_close() {
  $term -e sh -c "pass close"
  pass_val=$?
  if [ "$pass_val" -ne 0 ]; then
    die FATAL "failed to close tomb." -u "critical"
  else
    _notify "Your password tomb has been closed!" "normal" 0
  fi
}

deleteMenu() {
  delask=$(printf "1. Yes\n2. No" | _rofi -p '󰆴  Delete:' -mesg "<span color='${label}'>Really delete</span> <span color='${help_color}'>$menu?</span>")
  val=$?
  [ $val -eq 1 ] && {
    _notify "Cancelled!" "low" 0
    main
  }
  [ "$delask" = "1. Yes" ] && pass rm -f "$menu" && _notify "Deleted $menu" "normal" 0
  main
}

add_password() {
  length=${length:-72}
  addmenu=$(
    echo | _rofi -p '󰐕  New password:' -lines 0 \
      -theme-str 'window { width: 25em; } listview { lines: 0; } entry { placeholder: "Enter password name"; }'
  )
  val=$?
  if [ $val -eq 1 ]; then
    _notify "Cancelled!" "low" 0
    main
  elif [ $val -eq 0 ]; then
    if command -v "passgen.py" >/dev/null 2>&1; then
      passgen.py 3 | pass insert -e "$addmenu"
    else
      pass generate "$addmenu" "$length"
    fi
    if [ $val -eq 1 ]; then
      _notify ERROR "failed to add password" 0
      main
    else
      _notify "Added $addmenu" "normal" 0
    fi
  fi
  main
}

edit_password() {
  $term -e sh -c "export EDITOR=\"$editor\"; pass edit \"$menu\""
}

main() {
  enable_tomb=0
  enable_otp=0
  enable_zbar=0

  if ! command -v "tomb" >/dev/null 2>&1; then
    enable_tomb=1
  fi
  if ! command -v "zbarimg" >/dev/null 2>&1; then
    enable_zbar=1
  fi

  pass otp -h >/dev/null 2>&1 || enable_otp=1

  HELP=""

  HELP="$HELP<span color='${label}'>Actions: </span><span color='${help_color}'>${kb_add_pass}</span>: Add <span color='${div_color}'>|</span> <span color='${help_color}'>${kb_delete}</span>: Delete <span color='${div_color}'>|</span> <span color='${help_color}'>${kb_edit}</span>: Edit"

  if [ "$enable_zbar" -eq 0 ]; then
    ZBAR_HELP=" <span color='${div_color}'>|</span> <span color='${help_color}'>${kb_add_otp}</span>: Add OTP"
    HELP="$HELP$ZBAR_HELP"
  fi

  HELP="$HELP
<span color='${label}'>Copy: </span><span color='${help_color}'>Enter</span>: Copy Password <span color='${div_color}'>|</span> <span color='${help_color}'>${kb_copy_email}</span>: Copy Email"

  if [ "$enable_otp" -eq 0 ]; then
    OTP_HELP=" <span color='${div_color}'>|</span> <span color='${help_color}'>${kb_copy_otp}</span>: Copy OTP"
    HELP="$HELP$OTP_HELP"
  fi

  if [ "$enable_tomb" -eq 0 ]; then
    TOMB_HELP="<span color='${label}'>Tomb: </span><span color='${help_color}'>${kb_close_tomb}</span>: Close Tomb <span color='${div_color}'>|</span> <span color='${help_color}'>${kb_open_tomb}</span>: Open Tomb"
    HELP="$HELP
$TOMB_HELP"
  fi

  pass=$(find -L "$passdir" -type f -name '*.gpg' -printf '%P\n' | sed 's/\.gpg//')
  menu=$(echo "${pass}" | _rofi -p '󰌋  Pass:' -mesg "${HELP}" -kb-custom-1 "${kb_add_pass}" -kb-custom-2 "${kb_copy_otp}" -kb-custom-3 "${kb_delete}" -kb-custom-4 "${kb_edit}" -kb-custom-5 "${kb_close_tomb}" -kb-custom-6 "${kb_open_tomb}" -kb-custom-7 "${kb_copy_email}" -kb-custom-8 "${kb_add_otp}")

  val=$?
  case "$val" in
  1) exit ;;
  12) deleteMenu ;;
  11) # OTP Copy
    [ "$enable_otp" -eq 0 ] && {
      timeout 3 pass otp -c "$menu" || die ERROR "failed to copy OTP!" "normal"
      _notify "Copied to clipboard. Clearing in $time seconds" "normal"
      # clearboard
    }
    ;;
  10) add_password ;;
  13) edit_password ;;
  14) [ "$enable_tomb" -eq 0 ] && tomb_close ;;
  15) [ "$enable_tomb" -eq 0 ] && tomb_open ;;
  16) copy_email ;;
  17) [ "$enable_zbar" -eq 0 ] && append_otp ;;
  0)
    pass -c "$menu" || die ERROR "failed to copy the password!" "normal"
    _notify "Copied to clipboard. Clearing in $time seconds" "normal"
    # clearboard
    ;;
  esac
}

while getopts ":hvfl:t:T:e:" opt; do
  case "$opt" in
  h)
    usage
    exit 0
    ;;
  e)
    editor="$OPTARG"
    ;;
  v)
    version
    exit 0
    ;;
  f)
    FORCE=true
    ;;
  l)
    length="$OPTARG"
    ;;
  t)
    term="$OPTARG"
    ;;
  T)
    time="$OPTARG"
    ;;
  ?)
    printfc FATAL "invalid option '-$OPTARG'" >&2
    exit 1
    ;;
  esac
done

shift $((OPTIND - 1))

check_command rofi pass || exit 1

[ ! -d "$passdir" ] && die FATAL "no password directory found!" "low"

[ -z "$editor" ] && editor=${EDITOR:-nano}
term=${term:-kitty}
time=${time:-5}

main
