#!/usr/bin/env bash
# Bump version manifests, commit, push, tag, and create a GitHub Release for
# qdrddr/chunk-your-tools.
#
# Pushing vX.Y.Z triggers CI publish workflows (crates.io, npm, PyPI, C FFI).
# This script also creates the GitHub Release that publish-c-ffi.yml uploads to.
#
# Usage:
#   ./scripts/publish/publish-git.sh v1.0.4
#   ./scripts/publish/publish-git.sh bump-patch
#   ./scripts/publish/publish-git.sh bump-minor
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
EXPECTED_REPO="qdrddr/chunk-your-tools"

# shellcheck disable=SC1091
source "${SCRIPT_DIR}/../lib/shorten-paths.sh"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/versions.sh"

export SHORTEN_ROOT="${ROOT}"
publish_init_paths "${ROOT}"

usage() {
	cat <<EOF
Usage: $(basename "$0") TAG | bump-patch | bump-minor

Examples:
  $(basename "$0") v1.0.4
  $(basename "$0") bump-patch
  $(basename "$0") bump-minor

Auto-bump (bump-patch / bump-minor):
  - Fetch the latest git tags and GitHub releases matching vMAJOR.MINOR.PATCH
  - Pick the highest version among both
  - bump-patch: increment PATCH, e.g. v1.0.3 -> v1.0.4
  - bump-minor: increment MINOR and reset PATCH to 0, e.g. v1.0.3 -> v1.1.0

Steps:
  1. Run scripts/publish/sync-version.sh with the semver (without the leading v)
  2. Verify all SDK manifests match (Cargo, Python, npm, C, Go moduleversion)
  3. Commit only the version manifest files
  4. Push the current branch (expected: main)
  5. Force-create the git tag vX.Y.Z and push it
  6. Force-create the Go module tag sdk/go/vX.Y.Z and push it
  7. Create (or recreate) a GitHub Release for the tag (marked as pre-release)

Tag push triggers:
  - publish-crates.yml  -> crates.io chunk-your-tools
  - publish-npm-sdk.yml / publish-pypi-sdk.yml (after crates publish)
  - publish-c-ffi.yml   -> GitHub Release assets
EOF
}

validate_tag() {
	local tag="$1"
	if [[ ! "${tag}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
		echo "error: invalid tag (expected vX.Y.Z): ${tag}" >&2
		exit 1
	fi
}

require_command() {
	local cmd="$1"
	if ! command -v "${cmd}" >/dev/null 2>&1; then
		echo "error: required command not found: ${cmd}" >&2
		exit 1
	fi
}

require_repo() {
	local repo
	repo="$(gh repo view --json nameWithOwner --jq .nameWithOwner)"
	if [[ "${repo}" != "${EXPECTED_REPO}" ]]; then
		echo "error: expected GitHub repo ${EXPECTED_REPO}, got ${repo}" >&2
		exit 1
	fi
}

version_files() {
	publish_version_file_paths
}

semver_tag_pattern='^v[0-9]+\.[0-9]+\.[0-9]+$'

collect_version_tags() {
	git fetch origin --tags --quiet 2>/dev/null || true

	git tag -l 'v[0-9]*.[0-9]*.[0-9]*' |
		grep -E "${semver_tag_pattern}" || true

	gh release list --limit 1000 --json tagName --jq '.[].tagName' |
		grep -E "${semver_tag_pattern}" || true
}

latest_version_tag() {
	local -a versions=()

	mapfile -t versions < <(collect_version_tags | sort -uV)
	if ((${#versions[@]} == 0)); then
		echo "error: no vMAJOR.MINOR.PATCH tags or releases found; pass an explicit tag" >&2
		exit 1
	fi

	printf '%s\n' "${versions[-1]}"
}

resolve_bump_tag() {
	local bump_kind="$1"
	local latest major minor patch semver

	latest="$(latest_version_tag)"
	semver="${latest#v}"
	IFS='.' read -r major minor patch <<<"${semver}"

	case "${bump_kind}" in
	bump-patch)
		patch=$((patch + 1))
		;;
	bump-minor)
		minor=$((minor + 1))
		patch=0
		;;
	*)
		echo "error: unknown bump kind: ${bump_kind}" >&2
		exit 1
		;;
	esac

	printf 'v%s.%s.%s\n' "${major}" "${minor}" "${patch}"
}

previous_tag() {
	local tag="$1"
	git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-version:refname |
		while read -r candidate; do
			if [[ "${candidate}" != "${tag}" ]]; then
				printf '%s\n' "${candidate}"
				return 0
			fi
		done
}

release_notes() {
	local tag="$1"
	local prev_tag

	prev_tag="$(previous_tag "${tag}")"

	if [[ -n "${prev_tag}" ]]; then
		printf '**Full Changelog**: https://github.com/%s/compare/%s...%s\n' \
			"${EXPECTED_REPO}" "${prev_tag}" "${tag}"
	else
		printf 'Release %s\n' "${tag}"
	fi
}

push_tag() {
	local tag="$1"
	git tag -f "${tag}"
	git push -f origin "${tag}"
}

if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
	usage
	exit 0
fi

if [[ $# -ne 1 ]]; then
	usage >&2
	exit 1
fi

require_command git
require_command gh

arg="$1"
case "${arg}" in
bump-patch | bump-minor)
	tag="$(resolve_bump_tag "${arg}")"
	echo "${arg} resolved next tag: ${tag}"
	;;
*)
	tag="${arg}"
	;;
esac

validate_tag "${tag}"
semver="${tag#v}"
go_tag="$(publish_go_module_tag "${semver}")"

cd "${ROOT}"

if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
	echo "error: not inside a git repository" >&2
	exit 1
fi

require_repo

branch="$(git branch --show-current)"
if [[ -z "${branch}" ]]; then
	echo "error: detached HEAD; checkout a branch before publishing" >&2
	exit 1
fi
if [[ "${branch}" != "main" ]]; then
	echo "warning: publishing from branch ${branch} (expected main)" >&2
fi

mapfile -t files < <(version_files)

"${SCRIPT_DIR}/sync-version.sh" "${semver}"
publish_verify_versions "${semver}"

git add -- "${files[@]}"
if git diff --cached --quiet; then
	echo "version manifests already at ${semver}; skipping commit"
else
	git commit -m "version bump to ${tag}"
fi

git push origin HEAD

push_tag "${tag}"
push_tag "${go_tag}"

notes="$(release_notes "${tag}")"
if gh release view "${tag}" >/dev/null 2>&1; then
	gh release delete "${tag}" -y
fi
gh release create "${tag}" \
	--title "${tag}" \
	--notes "${notes}" \
	--prerelease

cat <<EOF | shorten_paths
published ${tag}:
  branch: ${branch}
  commit: $(git rev-parse --short HEAD)
  go tag: ${go_tag}
  release: https://github.com/${EXPECTED_REPO}/releases/tag/${tag}
EOF
