name: Release

on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  publish-npm:
    name: Publish to public npm
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # Release notes need every stable tag to pick their comparison base.
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org
          cache: npm

      - name: Update npm for trusted publishing
        run: npm install --global npm@^11.5.1

      - name: Install dependencies
        run: npm ci --ignore-scripts

      - name: Verify version matches tag
        run: |
          PKG_VERSION=$(node -p "require('./package.json').version")
          TAG_VERSION="${GITHUB_REF_NAME#v}"
          if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
            echo "Version mismatch: package.json=$PKG_VERSION tag=$TAG_VERSION"
            exit 1
          fi

      - name: Check if version already published
        run: |
          PKG_NAME=$(node -p "require('./package.json').name")
          PKG_VERSION=$(node -p "require('./package.json').version")
          if npm view "${PKG_NAME}@${PKG_VERSION}" version 2>/dev/null; then
            echo "ERROR: ${PKG_NAME}@${PKG_VERSION} is already published."
            exit 1
          fi

      - name: Build
        run: npm run build

      - name: Resolve dist-tag
        id: disttag
        run: |
          PKG_VERSION=$(node -p "require('./package.json').version")
          case "$PKG_VERSION" in
            *-*)
              # Prerelease (e.g. 0.21.0-beta.0). Derive the dist-tag from the
              # first prerelease identifier (beta, rc, alpha, ...) so it lands
              # on its own channel and NEVER on `latest` — the tag consumed by
              # the CLI's auto-update check (src/update.ts).
              PRE="${PKG_VERSION#*-}"
              DIST_TAG="${PRE%%.*}"
              # npm rejects dist-tags that look like a semver version, so fall
              # back to `beta` when the identifier isn't a plain word.
              case "$DIST_TAG" in
                ''|*[!a-zA-Z]*) DIST_TAG="beta" ;;
              esac
              IS_PRERELEASE=true
              ;;
            *)
              DIST_TAG="latest"
              IS_PRERELEASE=false
              ;;
          esac
          echo "Publishing $PKG_VERSION with dist-tag: $DIST_TAG (prerelease=$IS_PRERELEASE)"
          echo "dist_tag=$DIST_TAG" >> "$GITHUB_OUTPUT"
          echo "is_prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT"

      - name: Publish
        run: npm publish --access public --tag "${{ steps.disttag.outputs.dist_tag }}"

      - name: Resolve release notes base
        id: notesbase
        env:
          IS_PRERELEASE: ${{ steps.disttag.outputs.is_prerelease }}
        run: |
          # GitHub picks the base as the newest tag reachable from the tagged
          # commit. Release commits are created detached and never land on a
          # branch, so no release tag is an ancestor of the next one and the
          # base walks back to whichever old release happened to be merged.
          # Pick it explicitly instead.
          #
          # A prerelease compares against the tag right before it, keeping its
          # notes incremental; a stable release compares against the previous
          # stable tag. Either way, skip tags whose own parent is not in this
          # release's history: those were cut from an unrelated lineage (an
          # internal mirror sync), so their merge-base sits months back and
          # would drag hundreds of unrelated PRs into the notes.
          git config --add versionsort.suffix -beta
          git config --add versionsort.suffix -rc
          if [ "$IS_PRERELEASE" = "true" ]; then
            CANDIDATES=$(git tag -l 'v*' --sort=-v:refname)
          else
            CANDIDATES=$(git tag -l 'v*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$')
          fi
          PREV=""
          PAST_CURRENT=""
          for TAG in $CANDIDATES; do
            if [ "$TAG" = "$GITHUB_REF_NAME" ]; then
              PAST_CURRENT=1
              continue
            fi
            [ -n "$PAST_CURRENT" ] || continue
            if git merge-base --is-ancestor "$TAG^{commit}^1" "$GITHUB_REF_NAME^{commit}" 2>/dev/null; then
              PREV="$TAG"
              break
            fi
            echo "Skipping $TAG: cut from an unrelated lineage."
          done
          if [ -n "$PREV" ]; then
            echo "Release notes base: $PREV"
            echo "prev_tag=$PREV" >> "$GITHUB_OUTPUT"
          else
            echo "No usable earlier tag; letting GitHub pick the base."
          fi

      - name: Generate release notes
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PREV_TAG: ${{ steps.notesbase.outputs.prev_tag }}
        run: |
          ARGS=(-f tag_name="$GITHUB_REF_NAME")
          if [ -n "$PREV_TAG" ]; then
            ARGS+=(-f previous_tag_name="$PREV_TAG")
          fi
          gh api --method POST "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \
            "${ARGS[@]}" -q .body > "${RUNNER_TEMP}/release-notes.md"
          cat "${RUNNER_TEMP}/release-notes.md"

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          body_path: ${{ runner.temp }}/release-notes.md
          prerelease: ${{ steps.disttag.outputs.is_prerelease == 'true' }}