# ble/contrib/integration/bash-preexec.bash (C) 2022-2023, akinomyoga

## @arr[in] precmd_functions
## @arr[in] preexec_functions
##   The functions registered to these arrays are executed on PRECMD and
##   PREEXEC hooks, respectively.
##
## @fn[in] precmd
## @fn[in] preexec
##   If these functions are defined, they are executed on PRECMD and PREEXEC
##   hooks, respectively, through "precmd_functions" and "preexec_functions".
##
##
## * Integration with bash-preexec.sh (https://github.com/rcaloras/bash-preexec)
##
##   Although this script works without bash-preexec, it provides a better
##   integration with bash-preexec when bash-preexec is loaded.  The
##   integration relies on the following public API of bash-preexec.
##
##   @fn __bp_precmd_invoke_functions lastexit lastarg
##   @fn __bp_preexec_invoke_functions lastexit lastarg this_command
##   @fn __bp_uninstall
##   @var __bp_trapdebug_string
##   @var __bp_install_string

function ble/contrib/integration:bash-preexec/add-convenience-functions {
  ble/array#remove precmd_functions  precmd
  ble/array#remove preexec_functions preexec
  ble/array#unshift precmd_functions  precmd
  ble/array#unshift preexec_functions preexec
}

function ble/contrib/integration:bash-preexec/precmd.hook {
  local _ble_local_lastexit=$? _ble_local_lastarg=$_

  # Emulate bash-preexec variables
  __bp_last_ret_value=$_ble_local_lastexit
  __bp_last_argument_prev_command=$_ble_local_lastarg
  BP_PIPESTATUS=("${BLE_PIPESTATUS[@]}")
  local __bp_inside_precmd=1

  # local __bp_blesh_invoking_through_blesh=1 # XXX
  if ble/is-function __bp_precmd_invoke_functions; then
    __bp_precmd_invoke_functions "$_ble_local_lastexit" "$_ble_local_lastarg"
  else
    # For older bash-preexec.sh / without bash-preexec.sh
    local _ble_local_hook
    for _ble_local_hook in "${precmd_functions[@]}"; do
      if ble/bin#has "$_ble_local_hook"; then
        ble/util/setexit "$_ble_local_lastexit" "$_ble_local_lastarg"
        "$_ble_local_hook"
      fi
    done
  fi
}

function ble/contrib/integration:bash-preexec/preexec.hook {
  local _ble_local_lastexit=$? _ble_local_lastarg=$_ _ble_local_command=$1

  # Emulate bash-preexec variables
  local __bp_inside_preexec=1

  # local __bp_blesh_invoking_through_blesh=1 # XXX
  if ble/is-function __bp_preexec_invoke_functions; then
    __bp_preexec_invoke_functions "$_ble_local_lastexit" "$_ble_local_lastarg"
  else
    # For older bash-preexec.sh / without bash-preexec.sh
    local _ble_local_hook
    for _ble_local_hook in "${preexec_functions[@]}"; do
      if ble/bin#has "$_ble_local_hook"; then
        ble/util/setexit "$_ble_local_lastexit" "$_ble_local_lastarg"
        "$_ble_local_hook" "$_ble_local_command"
      fi
    done
  fi
}

## @fn ble/contrib/integration:bash-preexec/attach.hook
##   Remove bash-preexec hooks
function ble/contrib/integration:bash-preexec/attach.hook {
  local BP_TRAPDEBUG_STRING=${__bp_trapdebug_string:-'__bp_preexec_invoke_exec "$_"'}

  # Remove bash-preexec preexec hook in builtin DEBUG trap.
  local trap_string q="'" Q="'\''"
  ble/util/assign trap_string 'builtin trap -p DEBUG'
  if [[ $trap_string == "trap -- '${BP_TRAPDEBUG_STRING//$q/$Q}' DEBUG" ]]; then
    if [[ ${__bp_trap_string-} ]]; then
      builtin eval -- "builtin $__bp_trap_string"
    else
      builtin trap - DEBUG
    fi
  fi

  if ble/is-function __bp_uninstall; then
    __bp_uninstall
  else
    # For older bash-preexec.sh

    local BP_INSTALL_STRING=${__bp_install_string:-$'__bp_trap_string="$(trap -p DEBUG)"\ntrap - DEBUG\n__bp_install'}
    local BP_PROMPT_COMMAND_PREFIX=__bp_precmd_invoke_cmd
    local BP_PROMPT_COMMAND_SUFFIX=__bp_interactive_mode

    # Remove __bp_install hook from PROMPT_COMMAND
    if [[ ${PROMPT_COMMAND-} == *"$__bp_install_string"* ]]; then
      PROMPT_COMMAND="${PROMPT_COMMAND//$BP_INSTALL_STRING[;$'\n']}" # Edge case of appending to PROMPT_COMMAND
      PROMPT_COMMAND="${PROMPT_COMMAND//$BP_INSTALL_STRING}"
    fi

    # Remove precmd hook from PROMPT_COMMAND
    local i prompt_command
    for i in "${!PROMPT_COMMAND[@]}"; do
      prompt_command=${PROMPT_COMMAND[i]}
      case $prompt_command in
      ("$BP_PROMPT_COMMAND_PREFIX"|"$BP_PROMPT_COMMAND_SUFFIX")
        prompt_command= ;;
      (*)
        prompt_command=${prompt_command/#"$BP_PROMPT_COMMAND_PREFIX"$'\n'/$'\n'}
        prompt_command=${prompt_command%$'\n'"$BP_PROMPT_COMMAND_SUFFIX"}
        prompt_command=${prompt_command#$'\n'}
      esac
      PROMPT_COMMAND[i]=$prompt_command
    done

    # Remove preexec hook in the DEBUG trap
    local q="'" Q="'\''" trap_string
    ble/util/assign trap_string 'trap -p DEBUG'
    if [[ $trap_string == "trap -- '${BP_TRAPDEBUG_STRING//$q/$Q}' DEBUG" ]]; then
      if [[ ${__bp_trap_string-} ]]; then
        builtin eval -- "$__bp_trap_string"
      else
        trap - DEBUG
      fi
    fi
  fi
}
ble/function#trace ble/contrib/integration:bash-preexec/attach.hook

## @fn ble/contrib/integration:bash-preexec/detach.hook
function ble/contrib/integration:bash-preexec/detach.hook {
  # Reinstall bash-preexec hooks
  local BP_INSTALL_STRING=${__bp_install_string-}
  [[ ! $BP_INSTALL_STRING ]] && ble/is-function __bp_install &&
    BP_INSTALL_STRING=$'__bp_trap_string="$(trap -p DEBUG)"\ntrap - DEBUG\n__bp_install'

  builtin eval -- "$__bp_install_string"

  # Note: 重複して登録される (古い bash-preexec.sh) かもしれないし、全
  # く登録されない (bash-preexec.sh をロードしていない時) かもしれない
  # ので、ble.sh 側で末尾で一回呼び出す形に修正する。
  ble/contrib/integration:bash-preexec/add-convenience-functions
}

ble/contrib/integration:bash-preexec/add-convenience-functions
blehook PRECMD!=ble/contrib/integration:bash-preexec/precmd.hook
blehook PREEXEC!=ble/contrib/integration:bash-preexec/preexec.hook
blehook ATTACH!=ble/contrib/integration:bash-preexec/attach.hook
blehook DETACH!=ble/contrib/integration:bash-preexec/detach.hook
if [[ ${bash_preexec_imported-${__bp_imported-}} ]]; then
  ble/contrib/integration:bash-preexec/attach.hook
fi

# prevent bash-preexec.sh to be loaded
blehook ATTACH-=ble/contrib/integration:bash-preexec/loader
blehook POSTEXEC-=ble/contrib/integration:bash-preexec/loader
bash_preexec_imported=defined
__bp_imported=defined

# XXX: 以下は uninstall で削除しきれなかった時の為の保険。今の所不要に思われる。
# __bp_blesh_check() {
#   if [[ $BLE_ATTACHED && ! ${__bp_blesh_invoking_through_blesh-} ]]; then
#     ble/contrib/integration:bash-preexec/attach.hook
#   fi
# }
# precmd_function+=(__bp_blesh_check)
# preexec_function+=(__bp_blesh_check)

# Some settings rely on the internal APIs of bash-preexec.  For example, iTerm2
# shell integration uses "__bp_set_ret_value" and
# "$__bp_last_argument_prev_command".
function __bp_set_ret_value { return ${1:+"$1"}; }
