#!/usr/bin/env bash

# @file .config/log
# @brief Logger / prompt library that logs pretty console messages and provides several prompt methods
# @description
#   This file contains several functions that log content in different formats. It also provides an
#   interface for [Gum](https://github.com/charmbracelet/gum) based prompts. The available methods include:
#
#   * `choose` - Prompt user with choices
#   * `confirm` - Fancy Yes/No confirmation prompt
#   * `error` - Logs an error message
#   * `filter` - Filterable list of choices (with choices passed in as a line-return seperated file)
#   * `info` - Logs a regular message
#   * `input` - Prompt for a text input
#   * `md` - Render a markdown file with [Glow](https://github.com/charmbracelet/glow)
#   * `password` - Prompt for text that is masked by the prompt
#   * `prompt` - Log a description for a prompt that follows
#   * `spin` - Show a spinner while background job completes
#   * `star` - Logs a message with a star icon at the beginning
#   * `start` - Log a job start message
#   * `success` - Logs a success message
#   * `warn` - Logs a warning message
#   * `write` - Multi-line input prompt
#
#   If the `docker` environment variable is not set, the script / library will ensure both Gum and Glow are installed.

# @description Installs glow (a markdown renderer) from GitHub releases
# @example installGlow
installGlow() {
  # TODO: Add support for other architecture types
  if [ -d '/Applications' ] && [ -d '/Library' ] && [ -d '/Users' ]; then
    GLOW_DOWNLOAD_URL="https://github.com/charmbracelet/glow/releases/download/v1.4.1/glow_1.4.1_Darwin_x86_64.tar.gz"
  elif [ -f '/etc/ubuntu-release' ] || [ -f '/etc/debian_version' ] || [ -f '/etc/redhat-release' ] || [ -f '/etc/SuSE-release' ] || [ -f '/etc/arch-release' ] || [ -f '/etc/alpine-release' ]; then
    GLOW_DOWNLOAD_URL="https://github.com/charmbracelet/glow/releases/download/v1.4.1/glow_1.4.1_linux_x86_64.tar.gz"
  fi
  if type curl &> /dev/null; then
    if { [ -d '/Applications' ] && [ -d '/Library' ] && [ -d '/Users' ]; } || [ -f '/etc/ubuntu-release' ] || [ -f '/etc/debian_version' ] || [ -f '/etc/redhat-release' ] || [ -f '/etc/SuSE-release' ] || [ -f '/etc/arch-release' ] || [ -f '/etc/alpine-release' ]; then
      TMP="$(mktemp)"
      TMP_DIR="$(dirname "$TMP")"
      curl -sSL "$GLOW_DOWNLOAD_URL" > "$TMP"
      tar -xzf "$TMP" -C "$TMP_DIR"
      if [ -n "$HOME" ]; then
        if mkdir -p "$HOME/.local/bin" && mv "$TMP_DIR/glow" "$HOME/.local/bin/glow"; then
          GLOW_PATH="$HOME/.local/bin/glow"
        else
          GLOW_PATH="$(dirname "${BASH_SOURCE[0]}")/glow"
          mv "$TMP_DIR/gum" "$GLOW_PATH"
        fi
        chmod +x "$GLOW_PATH"
      else
        echo "WARNING: The HOME environment variable is not set! (Glow)"
      fi
    else
      echo "WARNING: Unable to detect system type. (Glow)"
    fi
  fi
}

# @description Installs gum (a logging CLI) from GitHub releases
# @example installGum
installGum() {
  # TODO: Add support for other architecture types
  if [ -d '/Applications' ] && [ -d '/Library' ] && [ -d '/Users' ]; then
    GUM_DOWNLOAD_URL="https://github.com/charmbracelet/gum/releases/download/v0.4.0/gum_0.4.0_Darwin_x86_64.tar.gz"
  elif [ -f '/etc/ubuntu-release' ] || [ -f '/etc/debian_version' ] || [ -f '/etc/redhat-release' ] || [ -f '/etc/SuSE-release' ] || [ -f '/etc/arch-release' ] || [ -f '/etc/alpine-release' ]; then
    GUM_DOWNLOAD_URL="https://github.com/charmbracelet/gum/releases/download/v0.4.0/gum_0.4.0_linux_x86_64.tar.gz"
  fi
  if type curl &> /dev/null; then
    if { [ -d '/Applications' ] && [ -d '/Library' ] && [ -d '/Users' ]; } || [ -f '/etc/ubuntu-release' ] || [ -f '/etc/debian_version' ] || [ -f '/etc/redhat-release' ] || [ -f '/etc/SuSE-release' ] || [ -f '/etc/arch-release' ] || [ -f '/etc/alpine-release' ]; then
      TMP="$(mktemp)"
      TMP_DIR="$(dirname "$TMP")"
      curl -sSL "$GUM_DOWNLOAD_URL" > "$TMP"
      tar -xzf "$TMP" -C "$TMP_DIR"
      if [ -n "$HOME" ]; then
        if mkdir -p "$HOME/.local/bin" && mv "$TMP_DIR/gum" "$HOME/.local/bin/gum"; then
          GUM_PATH="$HOME/.local/bin/gum"
        else
          GUM_PATH="$(dirname "${BASH_SOURCE[0]}")/gum"
          mv "$TMP_DIR/gum" "$GLOW_PATH"
        fi
        chmod +x "$GUM_PATH"
      else
        echo "WARNING: The HOME environment variable is not set! (Gum)"
      fi
    else
      echo "WARNING: Unable to detect system type. (Gum)"
    fi
  fi
}

# @description Configure the logger to use echo or gum
if [ "${container:=}" != 'docker' ]; then
  # Acquire gum's path or attempt to install it
  if type gum &> /dev/null; then
    GUM_PATH="$(which gum)"
  elif [ -f "$HOME/.local/bin/gum" ]; then
    GUM_PATH="$HOME/.local/bin/gum"
  elif [ -f "$(dirname "${BASH_SOURCE[0]}")/gum" ]; then
    GUM_PATH="$(dirname "${BASH_SOURCE[0]}")/gum"
  elif type brew &> /dev/null; then
    brew install --quiet gum
    GUM_PATH="$(which gum)"
  else
    if ! command -v qubesctl > /dev/null; then
      # Qubes dom0
      installGum
    fi
  fi

  # If gum's path was set, then turn on enhanced logging
  if [ -n "$GUM_PATH" ]; then
    chmod +x "$GUM_PATH"
    ENHANCED_LOGGING=true
  fi
fi

# @description Disable logging for Semantic Release because it tries to parse it as JSON
if [ -n "$SEMANTIC_RELEASE" ]; then
  NO_LOGGING=true
fi

# @description Logs using Gum
# @example logger info "An informative log"
logger() {
  TYPE="$1"
  MSG="$2"
  if [ "$TYPE" == 'error' ]; then
    "$GUM_PATH" style --border="thick" "$("$GUM_PATH" style --foreground="#ff0000" "✖") $("$GUM_PATH" style --bold --background="#ff0000" --foreground="#ffffff"  " ERROR ") $("$GUM_PATH" style --bold "$(format "$MSG")")"
  elif [ "$TYPE" == 'info' ]; then
    "$GUM_PATH" style " $("$GUM_PATH" style --foreground="#00ffff" "○") $("$GUM_PATH" style --faint --foreground="#ffffff" "$(formatFaint "$MSG")")"
  elif [ "$TYPE" == 'md' ]; then
    # @description Ensure glow is installed
    if [ "${container:=}" != 'docker' ]; then
      if type glow &> /dev/null; then
        GLOW_PATH="$(which glow)"
      elif [ -f "$HOME/.local/bin/glow" ]; then
        GLOW_PATH="$HOME/.local/bin/glow"
      elif [ -f "$(dirname "${BASH_SOURCE[0]}")/glow" ]; then
        GLOW_PATH="$(dirname "${BASH_SOURCE[0]}")/glow"
      elif type brew &> /dev/null; then
        brew install --quiet glow
        GLOW_PATH="$(which glow)"
      else
        if ! command -v qubesctl > /dev/null; then
          # Qubes dom0
          installGlow
        fi
      fi

      if [ -n "$GLOW_PATH" ]; then
        chmod +x "$GLOW_PATH"
        ENHANCED_LOGGING=true
      fi
    fi
    "$GLOW_PATH" "$2"
  elif [ "$1" == 'prompt' ]; then
    "$GUM_PATH" style " $("$GUM_PATH" style --foreground="#00008b" "▶") $("$GUM_PATH" style --bold "$(format "$2")")"
  elif [ "$1" == 'star' ]; then
    "$GUM_PATH" style " $("$GUM_PATH" style --foreground="#d1d100" "◆") $("$GUM_PATH" style --bold "$(format "$2")")"
  elif [ "$1" == 'start' ]; then
    "$GUM_PATH" style " $("$GUM_PATH" style --foreground="#00ff00" "▶") $("$GUM_PATH" style --bold "$(format "$2")")"
  elif [ "$1" == 'success' ]; then
    "$GUM_PATH" style " $("$GUM_PATH" style --foreground="#00ff00" "✔") $("$GUM_PATH" style --bold "$(format "$2")")"
  elif [ "$1" == 'warn' ]; then
    "$GUM_PATH" style " $("$GUM_PATH" style --foreground="#d1d100" "◆") $("$GUM_PATH" style --bold --background="#ffff00" --foreground="#000000"  " WARNING ") $("$GUM_PATH" style --bold "$(format "$2")")"
  else
    "$GUM_PATH" style " $("$GUM_PATH" style --foreground="#00ff00" "▶") $("$GUM_PATH" style --bold "$(format "$2")")"
  fi
}

format() {
  # shellcheck disable=SC2001,SC2016
  ANSI_STR_FORMATTED="$(echo "$1" | sed 's/^\([^`]*\)`\([^`]*\)`.*/\1\\u001b[47;30m \2 \\e[49;m/')"
  ANSI_STR="$(echo "$1" | sed 's/^\([^`]*\)`\([^`]*\)`\(.*\)$/\3/')"
  if [ "$ANSI_STR_FORMATTED" != "$ANSI_STR" ]; then
    if [[ $ANSI_STR == *'`'*'`'* ]]; then
      ANSI_STR_FORMATTED="$ANSI_STR_FORMATTED$(format "$("$GUM_PATH" style --bold "$ANSI_STR")")"
    else
      ANSI_STR_FORMATTED="$ANSI_STR_FORMATTED$("$GUM_PATH" style --bold "$ANSI_STR")"
    fi
  fi
  echo -e "$ANSI_STR_FORMATTED"
}

formatFaint() {
  # shellcheck disable=SC2001,SC2016
  ANSI_STR_FORMATTED="$(echo "$1" | sed 's/^\([^`]*\)`\([^`]*\)`.*/\1\\u001b[47;30m \2 \\e[49;m/')"
  # shellcheck disable=SC2001,SC2016
  ANSI_STR="$(echo "$1" | sed 's/^\([^`]*\)`\([^`]*\)`\(.*\)$/\3/')"
  if [ "$ANSI_STR_FORMATTED" != "$ANSI_STR" ]; then
    if [[ $ANSI_STR == *'`'*'`'* ]]; then
      ANSI_STR_FORMATTED="$ANSI_STR_FORMATTED$(formatFaint "$("$GUM_PATH" style --faint --foreground="#ffffff" "$ANSI_STR")")"
    else
      ANSI_STR_FORMATTED="$ANSI_STR_FORMATTED$("$GUM_PATH" style --faint --foreground="#ffffff" "$ANSI_STR")"
    fi
  fi
  echo -e "$ANSI_STR_FORMATTED"
}

# @description Display prompt that allows you to choose between options
# @example RESPONSE="$(.config/log choose "file.png" "another-file.jpg")"
choose() {
  if type gum &> /dev/null; then
    CHOOSE_ARGS="gum choose"
    for CURRENT_VAR in "$@"; do
      CHOOSE_ARGS="$CHOOSE_ARGS \"$CURRENT_VAR\""
    done
    eval $CHOOSE_ARGS
  else
    echo "ERROR: gum is not installed!"
  fi
}

# @description Display a confirmation prompt that returns an exit code if "No" is selected
# @example RESPONSE="$(.config/log confirm "Are you sure?" "Yeah" "Naa")"
confirm() {
  if type gum &> /dev/null; then
    GUM_OPTS=""
    if [ -n "$2" ]; then
      # shellcheck disable=SC089
      GUM_OPTS="$GUM_OPTS --affirmative=""'$2'"
    fi
    if [ -n "$3" ]; then
      GUM_OPTS="$GUM_OPTS --negative=""'$3'"
    fi
    if [ -n "$1" ]; then
      if [ -n "$GUM_OPTS" ]; then
        gum confirm "$1" "$GUM_OPTS"
      else
        gum confirm "$1"
      fi
    else
      gum confirm
    fi
  else
    echo "ERROR: gum is not installed!"
  fi
}

# @description Logs an error message
# @example .config/log error "Something happened!"
error() {
  if [ -z "$NO_LOGGING" ]; then
    if [ -n "$ENHANCED_LOGGING" ]; then
      logger error "$1"
    else
      echo -e "\e[1;41m  ERROR   \e[0m $(format "$1")\e[0;39m"
    fi
  fi
}

# @description Display a filterable prompt that is populated with options from a text file
# @example echo Strawberry >> flavors.text && echo Banana >> flavors.text && RESPONSE="$(.config/log filter flavors.txt)"
filter() {
  if type gum &> /dev/null; then
    TMP="$(mktemp)"
    gum filter < "$1"
  else
    echo "ERROR: gum is not installed!"
  fi
}

# @description Logs an info message
# @example .config/log info "Here is some information"
info() {
  if [ -z "$NO_LOGGING" ]; then
    if [ -n "$ENHANCED_LOGGING" ]; then
      logger info "$1"
    else
      echo -e "\e[1;46m   INFO   \e[0m $(format "$1")\e[0;39m"
    fi
  fi
}

# @description Displays an input with masked characters
# @example INPUT="$(.config/log input 'Enter the value..')"
input() {
  if type gum &> /dev/null; then
    if [ -n "$1" ]; then
      gum input --placeholder="$1"
    else
      gum input
    fi
  else
    echo "ERROR: gum is not installed!"
  fi
}

# @description Logs a message written in markdown
# @example .config/log md "[styled_link](https://google.com)"
# @example .config/log md mymarkdown/file.md
md() {
  if [ ! -f "$1" ]; then
    echo "ERROR: A markdown file must be passed in as the parameter" && exit 1
  fi
  if [ -n "$ENHANCED_LOGGING" ]; then
    logger md "$1"
  fi
}

# @description Displays an input with masked characters
# @example PASSWORD="$(.config/log password 'Enter the Ansible vault password')"
password() {
  if type gum &> /dev/null; then
    if [ -n "$1" ]; then
      gum input --password --placeholder="$1"
    else
      gum input --password
    fi
  else
    echo "ERROR: gum is not installed!"
  fi
}

# @description Logs a message that describes a prompt
# @example .config/log prompt "Enter text into the following prompt"
prompt() {
  if [ -z "$NO_LOGGING" ]; then
    if [ -n "$ENHANCED_LOGGING" ]; then
      logger prompt "$1"
    else
      echo -e "\e[1;104m  PROMPT  \e[0m $(format "$1")\e[0;39m"
    fi
  fi
}

# @description Display a spinner that stays until a command is completed
# @example .config/log spin "brew install yq" "Installing yq..")"
spin() {
  if type gum &> /dev/null; then
    if [ -n "$1" ] && [ -n "$2" ]; then
      gum spin --title="$2" "$1"
    elif [ -n "$1" ]; then
      gum spin "$1"
    else
      gum input
    fi
  else
    echo "ERROR: gum is not installed!"
  fi
}

# @description Logs a message that starts with a star emoji
# @example .config/log star "Congratulations"
star() {
  if [ -z "$NO_LOGGING" ]; then
    if [ -n "$ENHANCED_LOGGING" ]; then
      logger star "$1"
    else
      echo -e "\e[1;104m   LINK   \e[0m $(format "$1")\e[0;39m"
    fi
  fi
}

# @description Logs a message at the beginning of a task
# @example .config/log start "Starting the process.."
start() {
  if [ -z "$NO_LOGGING" ]; then
    if [ -n "$ENHANCED_LOGGING" ]; then
      logger start "$1"
    else
      echo -e "\e[1;46m   START  \e[0m $(format "$1")\e[0;39m"
    fi
  fi
}

# @description Logs a success message
# @example .config/log success "Yay!"
success() {
  if [ -z "$NO_LOGGING" ]; then
    if [ -n "$ENHANCED_LOGGING" ]; then
      logger success "$1"
    else
      echo -e "\e[1;42m SUCCESS  \e[0m $(format "$1")\e[0;39m"
    fi
  fi
}

# @description Logs a warning message
# @example .config/log warn "Just so you know.."
warn() {
  if [ -z "$NO_LOGGING" ]; then
    if [ -n "$ENHANCED_LOGGING" ]; then
      logger warn "$1"
    else
      echo -e "\e[1;43m WARNING  \e[0m $(format "$1")\e[0;39m"
    fi
  fi
}

# @description Displays a multi-line prompt for text input
# @example .config/log write "Write something..")"
write() {
  if type gum &> /dev/null; then
    if [ -n "$1" ]; then
      gum write --placeholder="$1"
    else
      gum write
    fi
  else
    echo "ERROR: gum is not installed!"
  fi
}

if [ -n "$1" ] && [ -n "$2" ]; then
  # Public functions that require at least two parameters to be used
  if [ "$1" == 'warn' ] || [ "$1" == 'success' ] || [ "$1" == 'star' ] || [ "$1" == 'info' ] \
  || [ "$1" == 'error' ] || [ "$1" == 'md' ] || [ "$1" == 'write' ] || [ "$1" == 'start' ] \
  || [ "$1" == 'spin' ] || [ "$1" == 'prompt' ] || [ "$1" == 'filter' ] || [ "$1" == 'input' ] \
  || [ "$1" == 'confirm' ] || [ "$1" == 'password' ]; then
    "$1" "$2"
  elif [[ "$1" == 'choose' ]]; then
    "$@"
  fi
elif [ -n "$1" ]; then
  # Public functions that can run with only one argument passed to .config/log (i.e. `.config/log password`)
  if [ "$1" == 'write' ] || [ "$1" == 'password' ] || [ "$1" == 'confirm' ] || [ "$1" == 'input' ]; then
    "$1"
  else
    info "$1"
  fi
fi