#!/usr/bin/env bash
# Pre-push hook: auto-bump a vX.Y.Z patch tag whenever HEAD has commits past
# the latest version tag. Keeps setuptools-scm's derived version in lockstep
# with what's deployed, so we never ship `0.0.0+unknown` or get mismatched
# health-endpoint version strings again.
#
# Bumps once per push. Skipped when:
#   - The push is itself a tag push (avoids loops)
#   - The push is NOT of the release branch (see below)
#   - HEAD is already tagged
#   - Env SKIP_AUTOTAG=1 is set
#
# Card 0c3668a6: this used to fire on ANY branch push, so pushing a feature
# branch cut a release tag at that branch's tip and pushed it, which triggers
# publish.yml and ships it to PyPI. Five of six consecutive tags were cut off
# unmerged branches that way, and skchat-sovereign was published straight from
# an in-review branch. Peer review stopped being a gate on what got released.
# So the hook now only tags a push of the release branch itself.
#
# Install:  scripts/hooks/install.sh
set -euo pipefail

#: The only branch a release tag may be cut from. Override for a fork whose
#: mainline is named differently.
RELEASE_BRANCH="${AUTOTAG_BRANCH:-main}"

if [[ "${SKIP_AUTOTAG:-0}" == "1" ]]; then
  exit 0
fi

# Read refs being pushed from stdin. Only a push of the RELEASE branch may cut a
# tag; tag pushes (loop guard) and feature-branch pushes are both skipped.
# Format: <local-ref> <local-sha> <remote-ref> <remote-sha>
pushing_release_branch=0
while read -r local_ref local_sha remote_ref remote_sha; do
  [[ -z "${local_ref:-}" ]] && continue
  case "$local_ref" in
    refs/tags/*) continue ;;
    "refs/heads/${RELEASE_BRANCH}") pushing_release_branch=1 ;;
  esac
done

if [[ "$pushing_release_branch" -eq 0 ]]; then
  exit 0
fi

# Belt and braces: even on a release-branch push, refuse to tag a commit that is
# not actually contained in the remote release branch. Catches a local main that
# has drifted or been reset onto something unmerged.
if git rev-parse -q --verify "refs/remotes/origin/${RELEASE_BRANCH}" >/dev/null 2>&1; then
  if ! git merge-base --is-ancestor HEAD "refs/remotes/origin/${RELEASE_BRANCH}" 2>/dev/null; then
    # HEAD is ahead of origin/<release>: that is a normal direct push of new
    # commits TO the release branch, which is exactly what we DO want to tag.
    # Only bail when HEAD is on a divergent line, i.e. origin/<release> is not
    # reachable from HEAD either.
    if ! git merge-base --is-ancestor "refs/remotes/origin/${RELEASE_BRANCH}" HEAD 2>/dev/null; then
      echo "[auto-tag] HEAD has diverged from origin/${RELEASE_BRANCH}; refusing to tag" >&2
      exit 0
    fi
  fi
fi

# Find the most recent vX.Y.Z tag reachable from HEAD.
latest=$(git describe --tags --abbrev=0 --match 'v[0-9]*' 2>/dev/null || echo "")
head_sha=$(git rev-parse HEAD)

if [[ -n "$latest" ]]; then
  tag_sha=$(git rev-list -n 1 "$latest")
  if [[ "$tag_sha" == "$head_sha" ]]; then
    # HEAD already tagged — nothing to do.
    exit 0
  fi
fi

# Default to v0.0.0 if no prior tag exists.
base="${latest:-v0.0.0}"
ver="${base#v}"
IFS='.' read -r major minor patch <<<"$ver"
major="${major:-0}"; minor="${minor:-0}"; patch="${patch:-0}"
patch=$((patch + 1))
next="v${major}.${minor}.${patch}"

# Avoid collisions if the tag already exists somehow.
while git rev-parse -q --verify "refs/tags/$next" >/dev/null 2>&1; do
  patch=$((patch + 1))
  next="v${major}.${minor}.${patch}"
done

echo "[auto-tag] $base -> $next at ${head_sha:0:8}" >&2
git tag -a "$next" -m "auto-bump on push"

# Push the tag alongside the branch so they land together. Use the same
# remote the user is pushing to (first arg to pre-push).
remote="${1:-origin}"
if ! SKIP_AUTOTAG=1 git push "$remote" "$next" >&2; then
  echo "[auto-tag] failed to push tag $next; rolling back local tag" >&2
  git tag -d "$next" >&2 || true
  exit 1
fi

# If multiple remotes exist (e.g., origin + forgejo), push to all configured
# remotes so we don't end up with one mirror behind on tags.
for r in $(git remote); do
  if [[ "$r" != "$remote" ]]; then
    SKIP_AUTOTAG=1 git push "$r" "$next" >&2 || \
      echo "[auto-tag] WARN: tag $next not pushed to $r" >&2
  fi
done

exit 0
