#!/usr/bin/env bash
# LICENSE: unlicense. This is free and unencumbered software released into the public domain.
# see unlicense.org for full license

RED="\033[0;31m"
GREEN="\033[0;32m"
YELLOW="\033[0;33m"
CLEAR="\033[0m"
MAIN_BRANCH=${MAIN_BRANCH:-main}
VERBOSE=

function usage {
    cat <<"EOF"
rmtree [-vh] [-m <MAIN_BRANCH>] <worktree directory to delete>

remove a worktree from a git repository. Assumes you're in a directory with
folders representing both a worktree and the main branch of your project.

Before removing, checks for any configured sync-back files that have changed
in the worktree and offers to copy them back to the main branch.

Configure sync-back files with:
    git config --add worktree.syncbackfiles "mise.local.toml"
    git config --add worktree.syncbackfiles "db.sql"

FLAGS:

    -h: print this help
    -v: verbose mode
    -m: MAIN_BRANCH defaults to `main`, and will check for `master` if that
        doesn't exist. Use this flag to pass a different main branch name.
        You may also set the MAIN_BRANCH environment variable to set the
        main branch name.

EOF
    exit 1
}

function die {
    # if verbose was set, and we're exiting early, make sure that we set +x to
    # stop the shell echoing verbosely
    if [ -n "$VERBOSE" ]; then
        set +x
    fi
    printf '%b%s%b\n' "$RED" "$1" "$CLEAR"
    exit 1
}

function err {
    printf '%b%s%b\n' "$YELLOW" "$1" "$CLEAR"
}

function warn {
    printf '%b%s%b\n' "$YELLOW" "$1" "$CLEAR"
}

# Check configured sync-back files for changes in the worktree, and offer to
# copy them back to the main branch before removal.
#
# Configure with: git config --add worktree.syncbackfiles "mise.local.toml"
function sync_back_files {
    local worktree_dir="$1"

    # Read sync-back files from git config (run from inside the worktree so we
    # pick up local config too)
    local syncback_files
    syncback_files=$(cd "$worktree_dir" && git config --get-all worktree.syncbackfiles 2>/dev/null)
    if [ -z "$syncback_files" ]; then
        return
    fi

    while IFS= read -r file; do
        local wt_file="$worktree_dir/$file"
        local main_file="$MAIN_BRANCH/$file"

        if [ ! -f "$wt_file" ]; then
            continue
        fi

        if [ ! -f "$main_file" ]; then
            warn "$file exists in worktree but not in main branch — copying back"
            mkdir -p "$(dirname "$main_file")"
            cp "$wt_file" "$main_file"
            continue
        fi

        # If the files are identical, skip
        if diff -q "$main_file" "$wt_file" > /dev/null 2>&1; then
            continue
        fi

        # Show the diff with delta and ask whether to copy back
        printf '%b%s has changed in the worktree:%b\n' "$YELLOW" "$file" "$CLEAR"
        delta "$main_file" "$wt_file"

        printf '%bCopy %s back to main branch? [y/N] %b' "$YELLOW" "$file" "$CLEAR"
        read -r answer </dev/tty
        if [[ "$answer" =~ ^[Yy]$ ]]; then
            cp "$wt_file" "$main_file"
            printf '%bCopied %s back to main branch%b\n' "$GREEN" "$file" "$CLEAR"
        fi
    done <<< "$syncback_files"
}

# rmtree <dir> will remove a worktree's directory, then prune the worktree list
# and delete the branch
function rmtree {
    if [ -n "$VERBOSE" ]; then
        set -x
    fi

    # verify that the first argument is a directory that exists, that we want
    # to remove
    if [ -z "$1" ]; then
        die "You must provide a directory name that is a worktree to remove"
    fi

    # for each argument, delete the directory and remove the worktree
    while [ -n "$1" ]; do
        if [ ! -d "$1" ]; then
            err "Unable to find directory $1, skipping"
            shift
            continue
        fi

        warn "removing $1"

        # verify that the main branch exists
        if [ ! -d "$MAIN_BRANCH" ]; then
            # for legacy reasons, check "master" as a possibility for the main
            # branch
            if [ -d "master" ] ; then
                MAIN_BRANCH=master
            else
                die "Could not find main branch directory <$MAIN_BRANCH>"
            fi
        fi

        branch_name=$(cd "$1" && git rev-parse --abbrev-ref HEAD)
        sync_back_files "$1"

        # Use trash if available, otherwise fall back to rm -rf. I do this
        # because trash is much faster on macs, where deleting a folder with
        # a venv or node_modules can take multiple minutes (!)
        if command -v trash >/dev/null 2>&1; then
            trash "$1"
        else
            warn "trash binary not found, using rm -rf instead"
            rm -rf "$1"
        fi

        (cd "$MAIN_BRANCH" && git worktree prune && git branch -D "$branch_name")

        shift
    done
}

while true; do
    case $1 in
        help | -h | --help)
            usage
            ;;
        -v | --verbose)
            VERBOSE=true
            shift
            ;;
        -m | --main-branch)
            MAIN_BRANCH=$2
            shift
            ;;
        *)
            break
            ;;
    esac
done

rmtree "$@"
