#!/usr/bin/env bash
# (c) 2026 Michał Górny
# SPDX-License-Identifier: GPL-2.0-or-later

VERSION=1

die() {
	echo "${@}" >&2
	exit 1
}

edo() {
	echo "+ ${@@Q}" >&2
	"${@}"
	local ret=${?}
	[[ ${ret} -ne 0 ]] && die "Command failed with exit status ${?}"
}

print_usage() {
	echo "Usage: ${0} [<options>]"
}

print_help() {
	print_usage
	echo
	echo "Create a Forgejo pull request for the current branch via AGit."
	echo
	echo "Options:"
	echo "  -e EDITOR, --editor EDITOR"
	echo "                   Override the editor used (default: \${EDITOR})"
	echo "  -r REMOTE, --remote REMOTE"
	echo "                   Specify the Codeberg remote name"
	echo "                   (default: search for codeberg.org SSH URL)"
	echo "  -T TARGET, --target TARGET"
	echo "                   Specify the target branch"
	echo "                   (default: first of dev, main, master on remote)"
	echo "  -t TOPIC, --topic TOPIC"
	echo "                   Specify the topic to use (default: branch name)"
	echo "  -h, --help       Print this help message"
	echo "  -V, --version    Print the version string"
}

find_remote() {
	local remotes=() r url
	mapfile -t remotes < <(git remote)
	# prefer [codeberg, upstream, origin], fall back to all branches
	for r in codeberg upstream origin "${remotes[@]}"; do
		url=$(git remote get-url "${r}")
		case ${url} in
			*ssh://git@codeberg.org/*|git@codeberg.org:*)
				echo "${r}"
				break
		esac
	done
}

find_target_branch() {
	local remote=${1}
	local candidate
	for candidate in dev main master; do
		if git rev-parse "${remote}/${candidate}" &>/dev/null; then
			echo "${candidate}"
			return
		fi
	done
}

get_template() {
	local target=${1}
	local path

	# matches forgejo behavior
	for path in .{forgejo,github}/pull_request_template.md; do
		git cat-file -p "${target}:${path}" 2>/dev/null && return
	done
}

fj_b64() {
	local data=${1}

	printf '{base64}'
	base64 -w 0 <<<"${data}"
}

main() {
	local editor=
	local remote=
	local target=
	local topic=

	while [[ ${#} -gt 0 ]]; do
		case ${1} in
			-h|--help)
				print_help
				exit 0
				;;
			-V|--version)
				echo "gagit ${VERSION}"
				exit 0
				;;
			-e|--editor)
				[[ ${#} -gt 1 ]] || die "${0}: missing argument to ${1}"
				editor=${2}
				shift
				;;
			-e*)
				editor=${1#-e}
				;;
			--editor=*)
				editor=${1#*=}
				;;
			-r|--remote)
				[[ ${#} -gt 1 ]] || die "${0}: missing argument to ${1}"
				remote=${2}
				shift
				;;
			-r*)
				remote=${1#-r}
				;;
			--remote=*)
				remote=${1#*=}
				;;
			-T|--target)
				[[ ${#} -gt 1 ]] || die "${0}: missing argument to ${1}"
				target=${2}
				shift
				;;
			-T*)
				target=${1#-T}
				;;
			--target=*)
				target=${1#*=}
				;;
			-t|--topic)
				[[ ${#} -gt 1 ]] || die "${0}: missing argument to ${1}"
				topic=${2}
				shift
				;;
			-t*)
				topic=${1#-t}
				;;
			--topic=*)
				topic=${1#*=}
				;;
			-*)
				print_usage >&2
				exit 1
				;;
			*)
				echo "${0}: no positional arguments expected" >&2
				print_usage
				exit 1
				;;
		esac
		shift
	done

	# set defaults
	: "${editor:=${EDITOR:-vim}}"

	if [[ -z ${remote} ]]; then
		remote=$(find_remote)
	fi
	[[ -z ${remote} ]] && die "Codeberg remote not found"
	echo "Codeberg remote: ${remote}" >&2

	local branch=$(git rev-parse --abbrev-ref HEAD)
	echo "Current branch: ${branch}" >&2
	case ${branch} in
		HEAD|dev|main|master)
			die "Please run gagit on a new feature branch"
			;;
	esac

	: "${topic:=${branch}}"

	if [[ -z ${target} ]]; then
		local target=$(find_target_branch "${remote}")
		[[ -z ${target} ]] && die "Unable to find a target branch"
	fi
	echo "Target branch: ${target}" >&2

	local template=$(get_template)

	tempdir=$(mktemp -d) || die "Unable to create a temporary directory"
	trap 'rm -r -f "${tempdir}"' EXIT
	cat > "${tempdir}/message" <<-EOF || die "Unable to write template"
		# Please edit this file to set the new pull request title and body.
		# All lines starting with '#' will be discarded.
		# An empty description will abort creating the PR.
		# ${branch} -> ${remote}/${target}

		# Put a single line title below.
		# "WIP:" creates a draft.  Remove it to skip draft stage.
		WIP: ${topic}

		# Put the pull request description below.

		# Commits are listed here for your convenience.
		$(git log --format='# %h %s' "${target}..${branch}")

		# Please leave the template below.
		# Either fill the boxes here or via the website after pushing.
		${template}
	EOF

	${editor} "${tempdir}/message"
	local line title body nl=$'\n'
	{
		# find the title
		while read -r line; do
			[[ ${line} == '#'* || -z ${line## } ]] && continue
			title=${line}
			break
		done
		# no title? sounds like we got an empty file (whitespace only)
		[[ -z ${title} ]] && die "No pull request description provided."

		# find the initial body line
		while read -r line; do
			[[ ${line} == '#'* || -z ${line## } ]] && continue
			body="${line}${nl}"
			break
		done

		# copy the remaining body lines
		while read -r line; do
			[[ ${line} == '#'* ]] && continue
			body+="${line}${nl}"
		done
	} < "${tempdir}/message"

	echo "Title: ${title}"
	edo git fetch "${remote}"
	edo git config "branch.${branch}.remote" "${remote}"
	edo git config "branch.${branch}.merge" "refs/for/${target}/${topic}"
	edo git push \
		-o "title=$(fj_b64 "${title}")" \
		-o "description=$(fj_b64 "${body}")"

	echo
	echo "The branch has been set to push to the PR.  To update it:"
	echo "  git push"
	echo
	echo "If you needed to force-push:"
	echo "  git push -o force-push=true"
	echo
}

main "${@}"
