functions.sh
################################################################################
##############################    git commands    ##############################
################################################################################

# git_stash_checkout_pull() {
#     git stash; [[ -n "$1" ]] && git checkout "$1"; git pull;
# }

# git_stash_checkout_pull() {
#     git stash
#     if [[ -n "$1" ]]; then
#         if [[ $(git ls-remote --heads origin "$1") ]]; then
#             git checkout -t "$1"
#         else
#             git checkout "$1"
#         fi
#     fi
#     git pull
# }

# git stash; git checkout; git pull
git_stash_checkout_pull() {
    git stash
    if [[ -n "$1" ]] && [[ "$1" != "--track-remote" ]]; then
        git checkout "$1"
    elif [[ "$1" == "--track-remote" && -n "$2" ]]; then
        git checkout -t "$2"
    fi
    git pull
}



################################################################################
##############################    helper commands    ##############################
################################################################################

run_in_terminal() {
  local wd=$(pwd)        # save working directory
  local cmd="$*"         # grab all args passed to function

  if [[ -z "$cmd" ]]; then
    echo "Usage: run_in_terminal <command>"
    return 1
  fi

  osascript <<EOF
tell application "Terminal"
    activate
    do script "cd '$wd' && $cmd"
end tell
EOF
}

################################################################################
#########################     REBASE ONTO LOGIC     ############################
################################################################################

function rebase_multiple_branches_onto {
  local target_branch="$1"

  # removes the first positional parameter ($1) and shifts all remaining parameters one position to the left
  shift

  local branch_history=()
  local debug_mode=false

  for arg in "$@"; do
    case $arg in
      --debug)
        debug_mode=true
        shift
        ;;
    esac
  done

  local branch=$(git branch --show-current)

  while [[ "$branch" != "$target_branch" ]]; do
    local create_commit=$(git reflog show --pretty='%H %gs' "$branch" | grep 'branch: Created from' | tail -n 1 | awk '{print $1}')

    # echo "branch: $branch"

    if [[ -z "$create_commit" ]]; then
      echo "Won't work: Branch creation commit not found."
      return 1
    fi

    # echo "create_commit: $create_commit"


    # ignore the current branch (starts with '*')
    local parent_branch=$(git branch --contains "$create_commit" | grep -v "$branch" | grep -v '^\*' | tail -n 1 | awk '{print $1}')


    if [[ -z "$parent_branch" ]]; then
      echo "Won't work: Parent branch not found."
      return 1
    fi

    # echo "parent_branch: $parent_branch"


    branch_history+=("$branch:$parent_branch:$create_commit")
    branch=$parent_branch

  done

  echo

  if $debug_mode; then
    echo "Here is the git commands that will run - if there is no missing data - the rebase should work ✅"
    echo 

    echo git stash

    for idx in {${#branch_history[@]}..1}; do
      entry="${branch_history[idx]}"

      IFS=':' read -r branch_name parent_branch commit_hash <<< "$entry"

      if [[ -n "$branch_name" && -n "$parent_branch" && -n "$commit_hash" ]]; then
        echo git checkout "$branch_name"
        echo git rebase --onto "$parent_branch" "$commit_hash"
      fi
    done

  elif [[ "$branch" == "$target_branch" ]]; then
    git stash

    for idx in {${#branch_history[@]}..1}; do
      entry="${branch_history[idx]}"

      IFS=':' read -r branch_name parent_branch commit_hash <<< "$entry"

      if [[ -n "$branch_name" && -n "$parent_branch" && -n "$commit_hash" ]]; then
        git checkout "$branch_name"
        git rebase --onto "$parent_branch" "$commit_hash"
      fi
    done
  else
    echo "Won't work: target branch '$target_branch' not reached."
  fi
}


################################################################################
##############################     DIFF LOGIC     ##############################
################################################################################

# Runs git diff command for `staged`, `modified`, and `untracked` files
diff_command() {
    local type="$1"  # "staged", "modified", or "untracked"

    if [[ "$type" == "staged" ]]; then
        git diff --cached --name-only
    elif [[ "$type" == "modified" ]]; then
        git diff --name-only
    elif [[ "$type" == "untracked" ]]; then
        git ls-files --others --exclude-standard
    else
        echo "Invalid type. Use 'staged', 'modified', or 'untracked'."
        return 1
    fi
}

# Helper function to get the full diff for a given action and file
_get_first_file() {
    local action="$1"  # "stage", "unstage", or "discard"

    if [[ "$action" == "stage" ]]; then
        diff_command 'modified' | head -n 1
    elif [[ "$action" == "unstage" ]]; then
        diff_command 'staged' | head -n 1
    elif [[ "$action" == "discard" ]]; then
        diff_command 'modified' | head -n 1
    else
        echo "Invalid action. Use 'stage', 'unstage', or 'discard'."
        return 1
    fi
}

# Helper function to get a nice diff header 
_get_diff_header() {
    local action="$1"  # "stage", "unstage", or "discard"
    local file="$2"
    local header="$3"

    if [[ -z "$file" ]]; then
        file=$(_get_first_file "$action")
    fi

    if [[ -z "$header" ]]; then
        full_diff=$(_get_full_diff "$action" "$file")
        header=$(_get_first_hunk "$full_diff" | grep '^@@')
    fi

    count=$(_get_hunk_count "$action" "$file")

    echo "----------------------------------"
    echo "file: $file ($count)"
    echo "hunk: $header"
    echo "----------------------------------"
}

# Helper function to get the number of hunks for the action and or file
_get_hunk_count() {
    local action="$1"  # "stage", "unstage", or "discard"
    local file="$2"

    if [[ -z "$file" ]]; then
        file=$(_get_first_file "$action")
    fi

    full_diff=$(_get_full_diff "$action" "$file")
    local count=$(echo "$full_diff" | grep -c '^@@')

    if (( count == 1 )); then
        echo "$count hunk"
    else
        echo "$count hunks"
    fi
}

# Helper function to get the full diff for a given action and file
_get_full_diff() {
    local action="$1"  # "stage", "unstage", or "discard"
    local file="$2"    # File path

    if [[ -z "$file" ]]; then
        file=$(_get_first_file "$action") || { echo 'get first file failed'; return 1; }
    fi

    if [[ "$action" == "stage" ]]; then
        git diff "$file"
    elif [[ "$action" == "unstage" ]]; then
        git diff --cached "$file"
    elif [[ "$action" == "discard" ]]; then
        git diff "$file"
    else
        echo "Invalid action. Use 'stage', 'unstage', or 'discard'."
        return 1
    fi
}

_get_first_hunk() {
    local full_diff="$1"

    echo "$full_diff" | awk '
      /^@@/ {
          if (found) exit;  # Exit if a second @@ line is found
          found = 1;        # Mark that the first @@ line is found
      }
      found { print }       # Print lines after finding the first @@
  '
}

# Helper function to get a hunk
_get_hunk() {
    local full_diff="$1"
    local header="$2"

    echo "$full_diff" | awk -v header="$header" '
      $0 == header { found = 1 }                  # Start printing when the line matches $header
      /^@@/ && $0 != header && found { exit }     # Stop printing if another @@ line is found
      found { print }                             # Print lines after $header is matched
    '
}

# Function to view a hunk (excluding @@ lines)
# ACTION="stage"      # "stage" or "unstage"
# FILE="[parent~2]"   # Optional, File path else uses the first file
# HEADER="[parent]"   # Optional, e.g., "@@ -17,6 +17,7 @@"
view_hunk() {
    local action="$1"
    local file="$2"
    local header="$3"

    local full_diff hunk
    full_diff=$(_get_full_diff "$action" "$file") || { echo 'full_diff failed'; return 1; }

    if [[ -n "$header" ]]; then
        hunk=$(_get_hunk "$full_diff" "$header") || { echo 'hunk failed'; return 1; }
    else
        hunk=$(_get_first_hunk "$full_diff") || { echo 'hunk failed'; return 1; }
    fi

    _get_diff_header "$action" "$file" "$header"
    echo

    # Remove all lines that start with @@
    echo "$hunk" | sed '/^@@/d'
}


# Function to process a hunk (stage or unstage)
# ACTION="stage"  # "stage" or "unstage"
# FILE="[parent~2]"   # Optional, File path else uses the first file
# HEADER="[parent]"   # Optional, e.g., "@@ -17,6 +17,7 @@" else uses first hunk
process_hunk() {
    local action="$1"
    local file="$2"
    local header="$3"

    # Determine the full diff and file count based on action
    local full_diff diff_header hunk file_count apply_command
    if [[ "$action" == "stage" ]]; then
        file_count=$(diff_command 'modified' | wc -l | xargs)
        apply_command="git apply --cached"
    elif [[ "$action" == "unstage" ]]; then
        file_count=$(diff_command 'staged' | wc -l | xargs)
        apply_command="git apply --cached --reverse"
    elif [[ "$action" == "discard" ]]; then
        file_count=$(diff_command 'modified' | wc -l | xargs)
        apply_command="git apply --reverse"
    else
        echo "Invalid action. Use 'stage', 'unstage', or 'discard'."
        return 1
    fi

    full_diff=$(_get_full_diff "$action" "$file") || (echo 'full_diff failed' && return 1)
    diff_header=$(echo "$full_diff" | head -n 4)

    if [[ -n "$header" ]]; then
        hunk=$(_get_hunk "$full_diff" "$header") || { echo 'hunk failed'; return 1; }
    else
        hunk=$(_get_first_hunk "$full_diff") || { echo 'hunk failed'; return 1; }
    fi

    # Create a patch file
    local patch_file="hunk.patch"
    {
        echo "$diff_header"
        echo "$hunk"
    } > "$patch_file"

    # Apply the patch using the determined command
    eval "$apply_command \"$patch_file\""

    # Cleanup
    rm "$patch_file"

    # Count the number of hunks
    local hunk_count
    hunk_count=$(echo "$full_diff" | grep -c '^@@')

    # Determine the reload level
    # viewing a specific file or a header
    if [[ -n "$header" || -n "$file" ]]; then

      if [[ "$hunk_count" -eq 1 && "$file_count" -eq 1 ]]; then
          return 3
      elif [[ "$hunk_count" -eq 1 ]]; then
          return 2
      else
          return 0
      fi

    else

      # viewing all hunks, only go back 2, if no more hunks
      if [[ "$hunk_count" -eq 1 && "$file_count" -eq 1 ]]; then
          return 2
      else
          return 0
      fi

    fi

}

# Function to process a file (stage or unstage)
# ACTION="stage"      # "stage" or "unstage" or "untracked"
# FILE="[parent~2]"   # File path else uses the first file
git_process_file() {
    local action="$1"
    local file="$2"

    local file_count
    if [[ "$action" == "stage" ]]; then
        file_count=$(diff_command 'modified' | wc -l | xargs)
        git add "$file"
    elif [[ "$action" == "unstage" ]]; then
        file_count=$(diff_command 'staged' | wc -l | xargs)
        git reset "$file"
    elif [[ "$action" == "untracked" ]]; then
        file_count=$(diff_command 'untracked' | wc -l | xargs)
        git add "$file"
    else
        echo "Invalid action. Use 'stage' or 'unstage''."
        return 1
    fi

    if [ "$file_count" -eq 1 ]; then
        return 1
    else
        return 0
    fi
}