name: Release

on:
  # Trigger on GitHub releases created by release-please.
  # release-please uses GITHUB_TOKEN which doesn't trigger tag-push events,
  # but the 'release' event IS dispatched for API-created releases.
  release:
    types: [published]
  # Also support manual tag pushes (e.g., pre-releases pushed via git)
  push:
    tags:
      - "v[0-9]+.[0-9]+.[0-9]+a[0-9]+" # alpha:   v1.2.3a1
      - "v[0-9]+.[0-9]+.[0-9]+b[0-9]+" # beta:    v1.2.3b1
      - "v[0-9]+.[0-9]+.[0-9]+rc[0-9]+" # rc:     v1.2.3rc1
  # Manual trigger for re-releasing missed versions
  workflow_dispatch:
    inputs:
      tag:
        description: "Tag to release (e.g., v0.15.0)"
        required: true
        type: string

permissions:
  contents: read

# Prevent concurrent releases
concurrency:
  group: release
  cancel-in-progress: false

# Resolve the tag name across all trigger types:
#   release event   → github.event.release.tag_name
#   push (tag)      → github.ref_name
#   workflow_dispatch → inputs.tag
env:
  RELEASE_TAG: ${{ github.event.release.tag_name || github.event.inputs.tag || github.ref_name }}

jobs:
  # ── Gate: full CI must pass before publishing ──────────────────────
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ env.RELEASE_TAG }}
          fetch-depth: 0  # full history for hatch-vcs

      - uses: astral-sh/setup-uv@v4
        with:
          version: "latest"

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: uv sync --group lint

      - name: Ruff check
        run: uv run ruff check .

      - name: Ruff format check
        run: uv run ruff format --check .

      - name: Mypy
        run: uv run mypy ziran/

  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: true
      matrix:
        python-version: ["3.11", "3.12", "3.13"]
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ env.RELEASE_TAG }}
          fetch-depth: 0  # full history for hatch-vcs

      - uses: astral-sh/setup-uv@v4
        with:
          version: "latest"

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies
        run: uv sync --group test --extra all

      - name: Run tests
        run: uv run pytest --cov=ziran --cov-report=term-missing -v

  # ── Build sdist + wheel ────────────────────────────────────────────
  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ env.RELEASE_TAG }}
          fetch-depth: 0  # full history for hatch-vcs

      # Force hatch-vcs / setuptools-scm to use the tag version.
      # git describe may pick the floating 'v0' tag over 'v0.3.0'
      # when both point to the same commit.
      - name: Pin package version from tag
        run: echo "SETUPTOOLS_SCM_PRETEND_VERSION=${RELEASE_TAG#v}" >> "$GITHUB_ENV"

      - uses: astral-sh/setup-uv@v4
        with:
          version: "latest"

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Build package
        run: uv build

      - name: Verify tag matches package version
        run: |
          PKG_VERSION=$(uv run python -c "
          from importlib.metadata import PathDistribution
          from pathlib import Path
          dists = list(Path('dist').glob('ziran-*.tar.gz'))
          # Extract version from filename: ziran-<version>.tar.gz
          name = dists[0].stem  # ziran-<version>.tar
          name = name.rsplit('.', 1)[0]  # ziran-<version>
          print(name.split('-', 1)[1])
          ")
          TAG_VERSION="${RELEASE_TAG#v}"
          echo "Package version: $PKG_VERSION"
          echo "Tag version:     $TAG_VERSION"
          if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
            echo "::error::Version mismatch — tag=$TAG_VERSION but package=$PKG_VERSION"
            exit 1
          fi

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          if-no-files-found: error
          retention-days: 5

  # ── Publish to PyPI (Trusted Publishing / OIDC) ───────────────────
  publish-pypi:
    needs: [build]
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/project/ziran/
    permissions:
      id-token: write  # required for OIDC trusted publishing
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/

      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

  # ── Publish to TestPyPI (pre-releases only) ────────────────────────
  publish-testpypi:
    if: >-
      contains(github.event.release.tag_name || github.event.inputs.tag || github.ref_name, 'a')
      || contains(github.event.release.tag_name || github.event.inputs.tag || github.ref_name, 'b')
      || contains(github.event.release.tag_name || github.event.inputs.tag || github.ref_name, 'rc')
    needs: [build]
    runs-on: ubuntu-latest
    environment:
      name: testpypi
      url: https://test.pypi.org/project/ziran/
    permissions:
      id-token: write
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/

      - name: Publish to TestPyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          repository-url: https://test.pypi.org/legacy/

  # ── Upload artifacts to GitHub Release ─────────────────────────────
  # When triggered by release-please (release event), the release already
  # exists — softprops/action-gh-release updates it with build artifacts.
  # When triggered by tag push (pre-releases), it creates a new release.
  github-release:
    needs: [publish-pypi]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4

      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/

      - name: Determine pre-release flag
        id: prerelease
        run: |
          if echo "${RELEASE_TAG}" | grep -qE '(a|b|rc)[0-9]+$'; then
            echo "flag=true" >> "$GITHUB_OUTPUT"
          else
            echo "flag=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Upload artifacts to GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ env.RELEASE_TAG }}
          name: ${{ env.RELEASE_TAG }}
          generate_release_notes: true
          prerelease: ${{ steps.prerelease.outputs.flag }}
          files: dist/*

  # ── Update floating major version tag for GitHub Actions ───────────
  # Consumers reference `taoq-ai/ziran@v0` — this tag always points
  # to the latest stable release so they automatically get patches.
  # Uses the GitHub API instead of git push to bypass tag protection rules.
  update-action-tag:
    if: >-
      !contains(github.event.release.tag_name || github.event.inputs.tag || github.ref_name, 'a')
      && !contains(github.event.release.tag_name || github.event.inputs.tag || github.ref_name, 'b')
      && !contains(github.event.release.tag_name || github.event.inputs.tag || github.ref_name, 'rc')
    needs: [github-release]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Update floating major tag
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          MAJOR=$(echo "${RELEASE_TAG}" | grep -oE '^v[0-9]+')
          SHA=$(gh api repos/${{ github.repository }}/git/ref/tags/${RELEASE_TAG} --jq '.object.sha')
          echo "Updating ${MAJOR} → ${SHA} (${RELEASE_TAG})"

          # Try to update existing tag ref; create if it doesn't exist
          if gh api repos/${{ github.repository }}/git/refs/tags/${MAJOR} --silent 2>/dev/null; then
            gh api --method PATCH repos/${{ github.repository }}/git/refs/tags/${MAJOR} \
              -f sha="${SHA}" -F force=true
          else
            gh api --method POST repos/${{ github.repository }}/git/refs \
              -f ref="refs/tags/${MAJOR}" -f sha="${SHA}"
          fi
