#!/usr/bin/env bash
#!/usr/bin/env -S -- bash -x

# constants

if [[ ! -v MIAOU_BASH_LOADED_ONCE ]]; then
  readonly MIAOU_BASH_LOADED_ONCE=true
  readonly MIAOU_DEBUG=${MIAOU_DEBUG:-false}
  readonly MIAOU_ERROR_REGEX='^(.*): line ([0-9]+): miaou_error:'

  declare -A ANSI
  ANSI[RESET]=0

  ANSI[STYLE_ITALIC]=3
  ANSI[STYLE_BOLD]=1
  ANSI[STYLE_UNDERLINE]=4
  ANSI[STYLE_STRIKETHROUGH]=9

  ANSI[FG_BLACK]=30
  ANSI[FG_RED]=31
  ANSI[FG_GREEN]=32
  ANSI[FG_YELLOW]=33
  ANSI[FG_BLUE]=34
  ANSI[FG_PURPLE]=35
  ANSI[FG_CYAN]=36
  ANSI[FG_WHITE]=37
  ANSI[FG_GRAY]=90
  ANSI[FG_LIGHTBLUE]=94
  ANSI[FG_MAGENTA]=95

  ANSI[BG_BLACK]=40
  ANSI[BG_RED]=41
  ANSI[BG_GREEN]=42
  ANSI[BG_YELLOW]=43
  ANSI[BG_BLUE]=44
  ANSI[BG_PURPLE]=45
  ANSI[BG_CYAN]=46
  ANSI[BG_WHITE]=47
  ANSI[BG_GRAY]=90
  ANSI[BG_MAGENTA]=95
  readonly ANSI
fi
## functions

function is_true {
  [[ "${1:-}" == 'true' ]]
}

# HINT: define an alias to bypass ARG evaluation when unnecessary!
# example: alias toto="bash -c \"[[ \\\$MIAOU_DEBUG == 'true' ]] && echo yes \\\$* || echo nope\""
function miaou_debug {
  if is_true "${MIAOU_DEBUG}"; then
    local saved_bash_rematch=("${BASH_REMATCH[@]}") # important when regex are used before calling miaou_debug
    local i message="$*" padding=''
    local compute_hierarchy=true
    [[ ! -v miaou_debug_hierarchy ]] && miaou_debug_hierarchy=0

    regex='^</' # starts with </
    [[ $miaou_debug_hierarchy -gt 0 ]] &&
      [[ "${message}" =~ $regex ]] &&
      miaou_debug_hierarchy=$((miaou_debug_hierarchy - 1)) &&
      compute_hierarchy=false

    for ((i = 0; i < miaou_debug_hierarchy; i++)); do padding+='  '; done
    >&4 builtin echo -e "\e[90m <== DEBUG ==> ${padding}\e[33m${message}\e[0m"

    regex='[^/]>$' # does NOT end with />
    is_true $compute_hierarchy &&
      [[ "${message}" =~ $regex ]] &&
      miaou_debug_hierarchy=$((miaou_debug_hierarchy + 1)) ||
      true
    BASH_REMATCH=("${saved_bash_rematch[@]}") # important when regex are used before calling miaou_debug
  fi
}

function on_return {
  local regex='return ([0-9]+)'
  if [[ "$BASH_COMMAND" =~ $regex ]] && [[ "${BASH_REMATCH[1]}" -gt 0 ]]; then
    failure_code="${BASH_REMATCH[1]}"
    failure_type="RETURN"
    failure_payload="${FUNCNAME[0]}"
    print_miaou_error "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${failure_code}" "${failure_type}" "${failure_payload}"
    miaou_debug "<on_return code=$failure_code payload=$failure_payload/>"
  fi
}

# overridden function which permits backtracing of the original `exit` statement
function exit {
  code=${1:-0}
  if [[ $code -gt 0 ]]; then
    error_source=("${BASH_SOURCE[@]}")
    error_lineno=("${BASH_LINENO[@]}")
    error_funcname=("${FUNCNAME[@]}")
    unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]'
    unset 'error_source[0]' 'error_lineno[-1]' 'error_funcname[0]'
    error_funcname[-1]='<main>'
    error_source=("${error_source[@]}")
    error_lineno=("${error_lineno[@]}")
    error_funcname=("${error_funcname[@]}")

    failure_code=${1:-0}
    failure_type="EXIT"
    failure_payload="${error_funcname[0]}"

    miaou_debug "<overridden_exit code=$failure_code/>"
    print_miaou_error "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${failure_code}" "${failure_type}" "${failure_payload}"
  fi
  builtin exit "$code"
}

function tty_ansi {
  local code
  for code in "$@"; do
    if [[ -v ANSI[$code] ]]; then
      >&4 echo -ne "\e[${ANSI[$code]}m"
    else
      >&2 echo "unknown ansi key: <$code>"
      break
    fi
  done
}

function print_miaou_error {
  local source="$1" line="$2" code="$3" type="$4" payload="${5:-}"
  local message
  message="${source}: line ${line}: miaou_error: ${type} ${code}"
  [[ -n "$payload" ]] && message+=" payload: ${payload}"
  >&2 echo "$message"
}

function add_miaou_error {
  local source="$1" line="$2" code="$3" type="$4" payload="${5:-}"
  local message
  message="${source}: line ${line}: miaou_error: ${type} ${code}"
  [[ -n "$payload" ]] && message+=" payload: ${payload}"
  tmp_error+=("$message")
}

function on_error {
  trap - RETURN
  failure_code="${1:-200}"

  miaou_debug "<on_error code=${failure_code} command={${BASH_COMMAND}}>"

  if [[ $$ == "$BASHPID" ]]; then
    if [[ ! -v failure_type ]]; then
      if [[ ${BASH_COMMAND} == 'false' ]]; then
        failure_type="FALSE"
        print_miaou_error "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${failure_code}" "${failure_type}"
      else
        failure_type="COMMAND_ERROR"
        failure_payload="${BASH_COMMAND%%$'\n'*}" # first line only
        print_miaou_error "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${failure_code}" "${failure_type}" "${failure_payload}"

        load_tmp_error # cannot print_miaou_error after investigation
        miaou_debug "<investigate_tmp_error pid=${BASHPID}>"

        if [[ ${#tmp_error[@]} -gt 1 ]]; then
          local last_error regex
          last_error="${tmp_error[-2]}" # the second latest cause previous print_miaou_error above

          regex='^(.*): line ([0-9]+): (.*): command not found$'
          if [[ $failure_code == 127 && "$last_error" =~ $regex ]]; then
            failure_type='COMMAND_NOT_FOUND'
            failure_payload="${BASH_REMATCH[3]}"
            miaou_debug "<command_not_found payload=${failure_payload}/>"
          else
            regex='^(.*): line ([0-9]+): (.*): No such file or directory$'
            if [[ $failure_code == 127 && "$last_error" =~ $regex ]]; then
              failure_type='FILE_NOT_FOUND'
              failure_payload="${BASH_REMATCH[3]}"
              miaou_debug "<file_not_found payload=${failure_payload}/>"

            elif ! is_true "$CHILD_PROCESS"; then
              # in main process only, not from children!
              local regex2 source lineno funcname
              last_error="${tmp_error[-1]}"
              regex2='^[[:space:]]+miaou_trace: source=(.*) lineno=(.*) funcname=(.*)$'
              if [[ "$last_error" =~ $regex2 ]]; then
                miaou_debug "<miaou_trace>"
                source="${BASH_REMATCH[1]}"
                lineno="${BASH_REMATCH[2]}"
                funcname="${BASH_REMATCH[3]}"
                extra_source=("$source")
                extra_lineno=("$lineno")
                extra_funcname=("$funcname")
                # miaou_debug "<miaou_trace found_first source=${extra_source} lineno=${extra_lineno} funcname=${extra_funcname}/>"
                # go backward
                for ((i = ${#tmp_error[@]} - 2; i >= 0; i--)); do
                  content=${tmp_error[$i]}
                  # miaou_debug "<index i=$i content=$content>"
                  if [[ "$content" =~ $regex2 ]]; then
                    source="${BASH_REMATCH[1]}"
                    lineno="${BASH_REMATCH[2]}"
                    funcname="${BASH_REMATCH[3]}"
                    extra_source=("$source" "${extra_source[@]}")
                    extra_lineno=("$lineno" "${extra_lineno[@]}")
                    extra_funcname=("$funcname" "${extra_funcname[@]}")
                    # miaou_debug '<miaou_trace found_remainders/>'
                  else
                    regex2='^(.*): line ([0-9]+): miaou_error: (.*) [0-9]+(.*)$'
                    if [[ "$content" =~ $regex2 ]]; then
                      failure_type='CHILD_ERROR'
                      failure_payload="${BASH_REMATCH[3]}"
                      local optional_payload
                      optional_payload="${BASH_REMATCH[4]}"
                      [[ -n "$optional_payload" ]] &&
                        optional_payload="${optional_payload# payload: }" &&
                        failure_payload="${failure_payload} ${optional_payload}"
                      miaou_debug "<child_error extra_traces=${#extra_source[@]} payload={${failure_payload}}/>"
                    else
                      miaou_debug '<child_error no_miaou_error weird=true/>'
                    fi
                    break
                  fi
                  # miaou_debug '</index>'
                done
                miaou_debug "</miaou_trace/>"
              fi
            fi
          fi
        fi
        miaou_debug '</investigate_tmp_error>'
      fi
    fi

    error_source=("${BASH_SOURCE[@]}")
    error_lineno=("${BASH_LINENO[@]}")
    error_funcname=("${FUNCNAME[@]}")
    unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]'
    unset 'error_source[0]' 'error_lineno[-1]' 'error_funcname[0]'
    error_funcname[-1]='<main>'

    if [[ $failure_type == 'CHILD_ERROR' ]]; then
      error_source=("${extra_source[@]}" "${error_source[@]}")
      error_lineno=("${extra_lineno[@]}" "${error_lineno[@]}")
      error_funcname=("${extra_funcname[@]}" "${error_funcname[@]}")

      # add the funcname hint message from backward
      local funcname hint_for_next=false
      for ((i = 0; i < ${#error_funcname[@]}; i++)); do
        is_true "$hint_for_next" &&
          error_funcname[i]+=' (hint: prefer source than child process)' &&
          hint_for_next=false
        funcname="${error_funcname[$i]}"
        [[ "$funcname" == '<main>' ]] && hint_for_next=true || true
      done

    else
      error_source=("${error_source[@]}")
      error_lineno=("${error_lineno[@]}")
      error_funcname=("${error_funcname[@]}")
    fi

    if [[ -v failure_payload ]]; then
      miaou_debug "</on_error exit=$failure_code type=${failure_type} payload={${failure_payload}}>"
    else
      miaou_debug "</on_error exit=$failure_code type=${failure_type}>"
    fi
    builtin exit "$failure_code"
  else
    print_miaou_error "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${failure_code}" 'SUBSHELL_ERROR' "${BASH_COMMAND}"
    miaou_debug "</on_error pipe=$failure_code sub_pid=$BASHPID pid=$$ command={${BASH_COMMAND}}>"
    kill -PIPE $$
  fi
}

function self_upgrade {
  . "$MIAOU_BASH_DIR"/lib/self-upgrade.bash
}

function uninstall {
  . "$MIAOU_BASH_DIR"/lib/uninstall.bash
}

function populate_error_arrays {

  local source=$1 line=$2 payload=$3
  unset 'error_source[0]' 'error_lineno[0]' 'error_funcname[0]'
  unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]'
  unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]'
  error_source=("$source" "${error_source[@]}")
  error_lineno=("$line" "${error_lineno[@]}")
  error_funcname=("${error_funcname[@]}" '<main>')
}

function on_pipe {
  trap - RETURN
  local tmp_error regex source lineno
  miaou_debug "<on_pipe sub_pid=$BASHPID pid=$$>"

  error_source=("${BASH_SOURCE[@]}")
  error_lineno=("${BASH_LINENO[@]}")
  error_funcname=("${FUNCNAME[@]}")
  unset 'error_source[0]' 'error_lineno[0]' 'error_funcname[0]'
  unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]'
  unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]'

  load_tmp_error
  last_error="${tmp_error[-1]}"

  regex='^(.*): line ([0-9]+): miaou_error: SUBSHELL_ERROR ([0-9]+) payload: (.*)$'
  if [[ $last_error =~ $regex ]]; then
    source=${BASH_REMATCH[1]}
    lineno=${BASH_REMATCH[2]}
    failure_code=${BASH_REMATCH[3]}
    failure_payload=${BASH_REMATCH[4]}
    failure_type="SUBSHELL_ERROR"
    miaou_debug "<subshell_error payload={${failure_payload}}"
    error_source=("$source" "${error_source[@]}")
    error_lineno=("$lineno" "${error_lineno[@]}")
    error_funcname=("${error_funcname[@]}" '<main>')
  else
    failure_code=141
    failure_type="PIPE_ERROR"
  fi
  miaou_debug "</on_pipe>"
  builtin exit "$failure_code"
}

function dump_stderr {
  local tmp_error_count=${#tmp_error[@]}
  miaou_debug "<stderr count=${tmp_error_count}>"
  if [[ -v MAIN_FD_MIAOU && $tmp_error_count -gt 0 && "$MAIN_FD_STDERR" == false ]]; then
    miaou_debug "<main_fd_miaou/>"
    local last_error="${tmp_error[-1]}"
    if [[ "$last_error" =~ $MIAOU_ERROR_REGEX ]]; then
      tmp_error_count=$((tmp_error_count - 1))
      miaou_debug "<hide_latest_miaou_error line=${last_error} count=$tmp_error_count/>"
      unset 'tmp_error[-1]'
      if [[ $tmp_error_count == 0 ]]; then
        # IDEA: grab last_stdout and test no carriage_return, cf: ../test/stub/tee_last_line.bash
        miaou_debug "<prevent_stdout_no_carriage_return />"
        printf "\r"
      fi
    fi
  fi
  tty_ansi FG_LIGHTBLUE
  for i in "${tmp_error[@]}"; do
    is_true "$CHILD_PROCESS" && miaou_debug "<childerr content={$i}/>"
    echo "$i"
  done
  tty_ansi RESET
  miaou_debug '</stderr>'
}

function dump_failure {
  local i
  miaou_debug '<error_box>'
  tty_ansi FG_MAGENTA
  length_box=$((${#failure_type} + ${#failure_code} + (2 * 2) + 2))
  [[ -v failure_payload ]] && length_box=$((length_box + ${#failure_payload} + (2 * 2) + 1))
  echo -n '╔'
  for ((i = 0; i < length_box; i++)); do echo -n '═'; done
  echo '╗'
  echo -n "║ $failure_type "
  [[ -v failure_payload ]] && echo -n ' ' && tty_ansi BG_WHITE FG_BLACK && echo -n "  $failure_payload  " && tty_ansi RESET
  tty_ansi FG_YELLOW
  printf "%5s" "  $failure_code  "
  tty_ansi FG_MAGENTA
  echo "║"

  if [[ ! -v error_source ]]; then
    echo -n '╚'
    for ((i = 0; i < length_box; i++)); do echo -n '═'; done
    echo '╝'
    miaou_debug '<continuation disabled/>'
  else
    echo -n '╚'
    for ((i = 0; i < 4; i++)); do echo -n '═'; done
    echo -n '╦'
    for ((i = 5; i < length_box; i++)); do echo -n '═'; done
    echo '╝'
    echo "     ║"
    miaou_debug '<continuation enabled/>'
  fi
  tty_ansi RESET
  miaou_debug '</error_box>'
}

function save_err_trace_to_main {
  miaou_debug "<save_stacktrace_to_stderr count=${#error_source[@]} to=${MIAOU_BASH_ERROR_CHILDREN}>"
  local i source lineno funcname
  for i in "${tmp_error[@]}"; do >>"$MIAOU_BASH_ERROR_CHILDREN" echo "$i"; done
  for i in "${!error_source[@]}"; do
    source="${error_source[$i]}"
    lineno="${error_lineno[$i]}"
    funcname="${error_funcname[$i]}"
    # miaou_debug "<trace source=${source} lineno=${lineno} funcname=${funcname}/>"
    >>"$MIAOU_BASH_ERROR_CHILDREN" echo -e "\tmiaou_trace: source=${source} lineno=${lineno} funcname=${funcname}"
  done
  miaou_debug "</save_stacktrace_to_stderr>"
}

function dump_stacktrace {
  miaou_debug "<stacktrace count=${#error_source[@]}>"
  local i source lineno funcname
  for i in "${!error_source[@]}"; do
    tty_ansi FG_MAGENTA
    echo -n "     ╟──▷"
    tty_ansi STYLE_ITALIC
    source="${error_source[$i]}"
    lineno="${error_lineno[$i]}"
    funcname="${error_funcname[$i]}"

    local hint regex='(^.*) \(hint: (.*)\)$'
    if [[ "$funcname" =~ $regex ]]; then
      hint="${BASH_REMATCH[2]}"
      funcname="${BASH_REMATCH[1]} (hint: "
    else
      unset hint
      [[ $i -gt 0 ]] && tty_ansi FG_GRAY
    fi

    printf "%40s:%.4s" "$source" "$lineno"
    tty_ansi RESET
    printf " \t%s" "$funcname"

    if [[ -v hint ]]; then
      tty_ansi FG_YELLOW
      builtin echo -n "$hint"
      tty_ansi RESET
      builtin echo -n ')'
    fi

    echo
  done
  miaou_debug '</stacktrace>'
}

function load_tmp_error {
  miaou_debug "<load_tmp_error from=$MIAOU_BASH_ERROR>"
  if [[ ! -v tmp_error ]]; then
    local count count_children tmp_error_children
    if [[ -f "$MIAOU_BASH_ERROR_CURRENT_CHILDREN" ]]; then
      miaou_debug "<tmp_children found=$MIAOU_BASH_ERROR_CURRENT_CHILDREN>"
      mapfile -t tmp_error_children <"$MIAOU_BASH_ERROR_CURRENT_CHILDREN"
      local count_children=${#tmp_error_children[@]}
      if [[ $count_children == 0 ]]; then
        miaou_debug "<tmp_error_children empty/>"
      else
        miaou_debug "<tmp_error_children count=${count_children}/>"
      fi
      miaou_debug "</tmp_children>"
      rm "$MIAOU_BASH_ERROR_CURRENT_CHILDREN"
    fi

    mapfile -t tmp_error <"$MIAOU_BASH_ERROR"

    if [[ -v count_children && $count_children -gt 0 ]]; then
      tmp_error=("${tmp_error[@]}" "${tmp_error_children[@]}")
    fi
    local count=${#tmp_error[@]}
    if [[ $count == 0 ]]; then
      miaou_debug "<tmp_error empty/>"
    else
      miaou_debug "<tmp_error count=${count}>"
      local i last_miaou_error_index=-1 content
      local multiple_errors=()
      for i in "${!tmp_error[@]}"; do
        content="${tmp_error[i]}"
        # miaou_debug "<tmperr index=$i content={$content}/>"
        if [[ "$content" =~ $MIAOU_ERROR_REGEX ]]; then
          if [[ $last_miaou_error_index -ge 0 ]]; then
            multiple_errors+=("$last_miaou_error_index")
          fi
          last_miaou_error_index=$i
        fi
      done
      if [[ "${#multiple_errors[@]}" -gt 0 ]]; then
        miaou_debug "<prune multiple_errors=(${multiple_errors[*]}) />"
        for i in "${multiple_errors[@]}"; do unset "tmp_error[$i]"; done
        tmp_error=("${tmp_error[@]}")
        miaou_debug "<tmp_error count=${#tmp_error[@]} clean/>"
      fi

      miaou_debug "</tmp_error>"
    fi
  else
    local count=${#tmp_error[@]}
    miaou_debug "<already_loaded count=$count/>"
  fi
  miaou_debug '</load_tmp_error>'
}

function on_exit {
  trap - RETURN
  failure_code="${1:-0}"
  local last_error regex
  miaou_debug "<on_exit code=$failure_code>"

  load_tmp_error

  if [[ $failure_code -gt 0 ]]; then
    if [[ -v failure_type && "$failure_type" == 'RETURN' ]]; then
      last_error="${tmp_error[-1]}"
      regex='^(.*): line ([0-9]+): miaou_error: RETURN [0-9]+'
      if [[ "$last_error" =~ $regex ]]; then
        miaou_debug "<return code=$failure_code/>"
        local source line
        source="${BASH_REMATCH[1]}"
        line="${BASH_REMATCH[2]}"
        error_source=("$source" "${error_source[@]}")
        error_lineno=("$line" "${error_lineno[@]}")
        error_funcname=("$failure_payload" "${error_funcname[@]}")
      else
        miaou_debug "<return type=unexpected/>"
      fi

    elif [[ -v failure_type && "$failure_type" == 'COMMAND_NOT_FOUND' ]]; then
      unset 'tmp_error[-1]' 'tmp_error[-1]'
      add_miaou_error "${BASH_SOURCE[2]}" "${BASH_LINENO[1]}" "${failure_code}" "${failure_type}" "${failure_payload}"
      miaou_debug "<command_not_found change_stderr count=${#tmp_error[@]}/>"

    elif [[ -v failure_type && "$failure_type" == 'FILE_NOT_FOUND' ]]; then
      unset 'tmp_error[-1]' 'tmp_error[-1]'
      add_miaou_error "${BASH_SOURCE[2]}" "${BASH_LINENO[1]}" "${failure_code}" "${failure_type}" "${failure_payload}"
      miaou_debug "<file_not_found change_stderr count=${#tmp_error[@]}/>"

    elif [[ -v failure_type && "$failure_type" == 'CHILD_ERROR' ]]; then
      for ((i = 0; i < ${#extra_source[@]}; i++)); do unset 'tmp_error[-1]'; done
      add_miaou_error "${BASH_SOURCE[2]}" "${BASH_LINENO[1]}" "${failure_code}" "${failure_type}" "${failure_payload}"
      miaou_debug "<child_error change_stderr count=${#tmp_error[@]}/>"

    elif [[ ! -v failure_type ]]; then
      if [[ ${#tmp_error[@]} -gt 0 ]]; then
        last_error="${tmp_error[-1]}"
        miaou_debug "<investigate_tmp_error last={$last_error}>"

        regex='^(.*): line ([0-9]+): (.*): unbound variable$'
        if [[ $failure_code -eq 1 ]] && [[ "$last_error" =~ $regex ]]; then
          failure_type='UNBOUND_VARIABLE'
          failure_payload="${BASH_REMATCH[3]}"
          failure_code=2

          local source="${BASH_REMATCH[1]}" line="${BASH_REMATCH[2]}"
          error_source=("${BASH_SOURCE[@]}")
          error_lineno=("${BASH_LINENO[@]}")
          error_funcname=("${FUNCNAME[@]}")
          populate_error_arrays "$source" "$line" "$failure_payload"

          unset 'tmp_error[-1]'
          add_miaou_error "$source" "$line" "${failure_code}" "${failure_type}" "${failure_payload}"
          miaou_debug "<unbound_variable source=${source} line=${line} payload=${failure_payload}/>"
        else
          regex='^(.*): line ([0-9]+): (.*): No such file or directory$'
          if [[ $failure_code -eq 1 ]] && [[ "$last_error" =~ $regex ]]; then
            failure_type='SOURCE_NOT_FOUND'
            failure_payload="${BASH_REMATCH[3]}"
            failure_code=3

            local source="${BASH_REMATCH[1]}" line="${BASH_REMATCH[2]}"
            error_source=("${BASH_SOURCE[@]}")
            error_lineno=("${BASH_LINENO[@]}")
            error_funcname=("${FUNCNAME[@]}")
            populate_error_arrays "$source" "$line" "$failure_payload"

            unset 'tmp_error[-1]'
            add_miaou_error "$source" "$line" "${failure_code}" "${failure_type}" "${failure_payload}"
            miaou_debug "<source_not_found source=${source} line=${line} payload=${failure_payload}/>"
          else
            failure_type='UNKNOWN ERROR'
            failure_payload="$BASH_COMMAND"
          fi
        fi
        miaou_debug "</investigate_tmp_error>"
      fi
    fi
  fi
  miaou_debug '<dump>'
  if ! is_true $CHILD_PROCESS; then
    2>&3 >&3 dump_stderr
    if [[ -v failure_type && $failure_code -gt 0 ]]; then
      2>&3 >&4 dump_failure
      if [[ -v error_source ]]; then
        2>&3 >&4 dump_stacktrace
      fi
    fi
  elif [[ -v failure_type ]]; then
    2>&3 >&4 save_err_trace_to_main
  fi
  miaou_debug '</dump>'

  if [[ $failure_code -gt 0 ]]; then
    miaou_debug "</on_exit code=$failure_code>"
  else
    miaou_debug "</on_exit success>"
  fi
  cleanup
}

function build_miaou_bash_error {
  local directory
  directory="/tmp/$(basename "$0")-$UID"

  CHILD_PROCESS=false
  [[ -f "${directory}/${PPID}.tmp" ]] && CHILD_PROCESS=true

  mkdir -p "$directory"
  MIAOU_BASH_ERROR_CURRENT_CHILDREN="${directory}/$$.children.tmp"
  MIAOU_BASH_ERROR_CHILDREN="${directory}/${PPID}.children.tmp"
  MIAOU_BASH_ERROR="${directory}/$$.tmp"
  rm -f "$MIAOU_BASH_ERROR"

  readonly MIAOU_BASH_ERROR_CURRENT_CHILDREN MIAOU_BASH_ERROR_CHILDREN MIAOU_BASH_ERROR CHILD_PROCESS
}

function cleanup {
  miaou_debug '<cleanup />'
  trap - ERR EXIT PIPE RETURN
  rm -f "$MIAOU_BASH_ERROR"
}

function usage {
  echo "$(basename "$0") <SCRIPT> [...SCRIPT_ARGS] | --version | --help | --self-upgrade | --initiate | --uninstall"
}

function version {
  cat "${MIAOU_BASH_DIR:-/opt/miaou-bash}/.semver_git_tag"
}

function parse_options {
  HEREDOC=false
  if [[ $# == 0 ]]; then
    if [[ -t 0 ]]; then
      >&2 echo 'ERROR: script expected!'
      builtin exit 2
    else
      HEREDOC=true
      return
    fi
  elif [[ $# == 1 ]]; then
    case "$1" in
    --help) usage && builtin exit ;;
    --version) version && builtin exit ;;
    --self-upgrade) self_upgrade && builtin exit ;;
    --uninstall) uninstall && builtin exit ;;
    -*) >&2 echo "ERROR: option '$1' unknown!" && usage && builtin exit 2 ;;
    esac
  fi

  [[ $1 =~ ^- ]] && >&2 echo "ERROR: too many args $#, either single option or SCRIPT + args accepted!" && builtin exit 2

  SCRIPT="$1"
  if [[ ! -f "$SCRIPT" ]]; then
    >&2 echo "ERROR: script '$SCRIPT' not found!"
    builtin exit 3
  fi
}

function read {
  # shellcheck disable=SC2162
  2>&1 builtin read "$@"
}

## main

set -euo pipefail
set -E # HALT on pipe error
set -T # RETURN

parse_options "$@"
build_miaou_bash_error
trap 'on_error $?' ERR
trap 'on_exit $?' EXIT
trap 'on_pipe' PIPE
trap 'on_return' RETURN

[[ -v SCRIPT ]] && BASH_ARGV0="${SCRIPT}" && shift

if is_true "$CHILD_PROCESS"; then
  # any nested children
  exec 4>/dev/tty 2>"$MIAOU_BASH_ERROR"
  miaou_debug "<child file=$BASH_ARGV0 pid=${BASHPID} parent=${PPID}>"
else
  # the MAIN Process
  if [[ -e /proc/$$/fd/3 ]]; then
    exec 4>&3 3>&- # swap fd 3 4 when fd 3 sent off
  else
    MAIN_FD_MIAOU=true
    [[ -t 2 ]] && MAIN_FD_STDERR=false || MAIN_FD_STDERR=true
    exec 4>/dev/tty
  fi
  exec 3>&2 2>"$MIAOU_BASH_ERROR"
  miaou_debug "<main file=$BASH_ARGV0 pid=${BASHPID}/>"
fi

if [[ "$HEREDOC" == true ]]; then
  # shellcheck source=/dev/stdin
  source /dev/stdin
else
  # shellcheck source=/dev/null
  source "$SCRIPT"
fi
