name: Release Selenium

on:
  pull_request:
    types: [closed]
    paths:
      - 'java/version.bzl'
      - 'rb/lib/selenium/webdriver/version.rb'
      - 'py/selenium/__init__.py'
      - 'dotnet/version.bzl'
      - 'javascript/selenium-webdriver/package.json'
  workflow_dispatch:
    inputs:
      tag:
        description: 'Release tag (e.g., selenium-4.28.0 or selenium-4.28.1-ruby)'
        required: true

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

permissions:
  contents: write
  packages: write

jobs:
  extract-tag:
    name: Extract Tag
    runs-on: ubuntu-latest
    if: >
      github.event.repository.fork == false &&
      ((startsWith(github.event.pull_request.head.ref, 'release-preparation-') &&
       github.event.pull_request.merged == true) ||
      (github.event_name == 'workflow_dispatch' && github.event.inputs.tag != ''))
    outputs:
      tag: ${{ steps.extract.outputs.tag }}
    steps:
      - name: Extract tag from input or PR branch
        id: extract
        env:
          EVENT_NAME: ${{ github.event_name }}
          INPUT_TAG: ${{ inputs.tag }}
          PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
        run: |
          if [ "$EVENT_NAME" == "workflow_dispatch" ]; then
            TAG="$INPUT_TAG"
          else
            # Extract tag from branch name: release-preparation-selenium-4.28.1-ruby -> selenium-4.28.1-ruby
            TAG="${PR_HEAD_REF#release-preparation-}"
          fi
          echo "tag=$TAG" >> "$GITHUB_OUTPUT"

  parse-tag:
    name: Parse Tag
    needs: extract-tag
    uses: ./.github/workflows/parse-release-tag.yml
    with:
      tag: ${{ needs.extract-tag.outputs.tag }}

  get-approval:
    name: Get Approval
    needs: parse-tag
    uses: ./.github/workflows/get-approval.yml
    with:
      title: Release approval required
      message: "Approval is required to publish ${{ needs.parse-tag.outputs.tag }}."
    secrets:
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

  create-tag:
    name: Create Tag
    needs: [parse-tag, get-approval]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Create release tag
        env:
          GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
          TAG: ${{ needs.parse-tag.outputs.tag }}
          SHA: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
        run: |
          if gh api "/repos/${{ github.repository }}/git/ref/tags/${TAG}" >/dev/null 2>&1; then
            echo "Tag ${TAG} already exists — skipping creation."
            exit 0
          fi
          gh api -X POST /repos/${{ github.repository }}/git/refs \
            -f ref="refs/tags/${TAG}" \
            -f sha="${SHA}"

  github-release-draft:
    name: GitHub Release Draft
    needs: [parse-tag, get-approval]
    if: needs.parse-tag.outputs.language == 'all'
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Checkout repo
        uses: actions/checkout@v6
        with:
          persist-credentials: false
      - name: Generate release notes against previous minor release
        env:
          TAG: ${{ needs.parse-tag.outputs.tag }}
          SHA: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
        run: |
          PREV=$(gh release list --exclude-drafts --json tagName \
            -q 'map(.tagName | select(test("^selenium-[0-9]+\\.[0-9]+\\.0$")))[0]')
          echo "Previous release tag: $PREV"
          {
            cat scripts/github-actions/release_header.md
            echo ""
            gh api -X POST "/repos/${{ github.repository }}/releases/generate-notes" \
              -f tag_name="$TAG" \
              -f previous_tag_name="$PREV" \
              -f target_commitish="$SHA" \
              --jq .body
          } > release_body.md
      - name: Create draft GitHub release
        uses: ncipollo/release-action@v1
        with:
          draft: true
          allowUpdates: true
          bodyFile: "release_body.md"
          name: "Selenium ${{ needs.parse-tag.outputs.version }}"
          tag: "${{ needs.parse-tag.outputs.tag }}"
          commit: ${{ github.event.pull_request.merge_commit_sha || github.sha }}

  publish:
    name: Build and Publish ${{ matrix.language }}
    needs: [parse-tag, get-approval]
    permissions:
      contents: read
      id-token: write
    strategy:
      fail-fast: false
      matrix:
        language: [java, ruby, dotnet, javascript]
    uses: ./.github/workflows/bazel.yml
    with:
      name: Publish ${{ matrix.language }}
      gpg-sign: ${{ matrix.language == 'java' }}
      gem-trusted-publishing: ${{ matrix.language == 'ruby' }}
      node-version: ${{ matrix.language == 'javascript' && '24' || '' }}
      run: |
        if [ "${{ matrix.language == 'java' && (needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == 'java') && github.run_attempt > 1 }}" = "true" ]; then
          echo "::error::Java release is not yet rerun-safe — check/drop the staging repo at https://central.sonatype.com/publishing/deployments and publish manually"
          exit 1
        fi
        if [ "${{ needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == matrix.language }}" = "true" ]; then
          ./go ${{ matrix.language }}:release
        else
          echo skipping
        fi
      artifact-name: ${{ (needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == matrix.language) && format('release-packages-{0}', matrix.language) || '' }}
      artifact-path: ${{ (matrix.language == 'java' || matrix.language == 'dotnet') && 'build/dist/*.*' || '' }}
    secrets: inherit

  build-python:
    name: Build Python
    needs: [parse-tag, get-approval]
    if: needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == 'python'
    uses: ./.github/workflows/bazel.yml
    with:
      name: Build Python
      run: ./go py:build --config=release
      artifact-name: pypi-distributions
      artifact-path: |
        bazel-bin/py/selenium-*.whl
        bazel-bin/py/selenium-[0-9]*.tar.gz
    secrets: inherit

  publish-python:
    name: Publish Python to PyPI
    needs: [parse-tag, build-python]
    if: needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == 'python'
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
      actions: read
    steps:
      - name: Download Python distributions
        uses: actions/download-artifact@v8
        with:
          name: pypi-distributions
          path: dist/
      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          skip-existing: true

  github-release-publish:
    name: GitHub Release Publish
    needs: [parse-tag, publish, github-release-draft]
    if: >-
      always() && !failure() && !cancelled() &&
      (needs.parse-tag.outputs.language == 'all' ||
      needs.parse-tag.outputs.language == 'java' ||
      needs.parse-tag.outputs.language == 'dotnet')
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Checkout repo
        if: needs.parse-tag.outputs.language == 'all'
        uses: actions/checkout@v6
        with:
          persist-credentials: false
      - name: Download all release packages
        if: needs.parse-tag.outputs.language == 'all'
        uses: actions/download-artifact@v8
        with:
          pattern: release-packages-*
          merge-multiple: true
          path: "build/dist/"
      - name: Download release packages
        if: needs.parse-tag.outputs.language != 'all'
        uses: actions/download-artifact@v8
        with:
          name: release-packages-${{ needs.parse-tag.outputs.language }}
          path: "build/dist/"
      - name: Delete nightly release and tag
        if: needs.parse-tag.outputs.language == 'all'
        env:
          GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
        run: |
          if gh release view nightly >/dev/null 2>&1; then
            gh release delete nightly --yes
          fi
          if git ls-remote --tags origin refs/tags/nightly | grep -q nightly; then
            gh api -X DELETE /repos/${{ github.repository }}/git/refs/tags/nightly
          fi
      - name: Upload to GitHub release
        env:
          GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
          VERSION: ${{ needs.parse-tag.outputs.version }}
        run: |
          BASE_VERSION="${VERSION%.*}.0"
          gh release upload "selenium-$BASE_VERSION" build/dist/*.* --clobber
          gh release edit "selenium-$BASE_VERSION" --draft=false

  docs:
    name: Update ${{ matrix.language }} Documentation
    needs: [parse-tag, publish, publish-python, github-release-publish, create-tag]
    if: >-
      always() && !cancelled() &&
      needs.publish.result == 'success' &&
      (needs.publish-python.result == 'success' || needs.publish-python.result == 'skipped') &&
      needs.github-release-publish.result != 'failure' &&
      needs.create-tag.result != 'failure'
    permissions:
      contents: write
    strategy:
      fail-fast: false
      matrix:
        language: [java, python, ruby, dotnet, javascript]
    uses: ./.github/workflows/update-documentation.yml
    with:
      tag: ${{ needs.parse-tag.outputs.tag }}
      language: ${{ matrix.language }}
      skip: ${{ needs.parse-tag.outputs.language != 'all' && needs.parse-tag.outputs.language != matrix.language }}
    secrets:
      SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}

  verify:
    name: Verify Published Packages
    needs: [parse-tag, docs]
    uses: ./.github/workflows/bazel.yml
    with:
      name: Verify packages
      run: ./go ${{ needs.parse-tag.outputs.language }}:verify

  unrestrict-trunk:
    name: Unrestrict Trunk Branch
    needs: verify
    uses: ./.github/workflows/restrict-trunk.yml
    with:
      restrict: false
    secrets:
      SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

  reset-version:
    name: Generate Nightly Versions
    needs: [parse-tag, docs]
    uses: ./.github/workflows/bazel.yml
    with:
      name: Reset Versions
      run: ./go ${{ needs.parse-tag.outputs.language }}:version nightly${{ needs.parse-tag.outputs.language == 'all' && ' && ./go rust:version nightly' || '' }}
      artifact-name: version-reset

  update-version:
    name: Push Nightly Versions
    needs: [parse-tag, reset-version, unrestrict-trunk]
    permissions:
      contents: write
      actions: read
    uses: ./.github/workflows/commit-changes.yml
    with:
      artifact-name: version-reset
      commit-message: "[build] Reset versions to nightly after ${{ needs.parse-tag.outputs.tag }} release"
    secrets:
      SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}

  reset-dependencies:
    name: Update Lockfiles for Nightly
    needs: [parse-tag, update-version]
    uses: ./.github/workflows/bazel.yml
    with:
      name: Reset Dependencies
      run: ./go ${{ needs.parse-tag.outputs.language }}:pin${{ needs.parse-tag.outputs.language == 'all' && ' && ./go rust:pin' || '' }}
      artifact-name: version-reset-deps

  commit-dependencies:
    name: Push Nightly Lockfiles
    needs: [parse-tag, reset-dependencies]
    permissions:
      contents: write
      actions: read
    uses: ./.github/workflows/commit-changes.yml
    with:
      artifact-name: version-reset-deps
      commit-message: "[build] Update lockfiles for nightly versions"
    secrets:
      SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}

  nightly:
    name: Publish Nightly Packages
    needs: [parse-tag, commit-dependencies]
    permissions:
      contents: write
      packages: write
    uses: ./.github/workflows/nightly.yml
    with:
      language: ${{ needs.parse-tag.outputs.language }}
    secrets: inherit

  mirror:
    name: Update Release Mirror
    needs: [parse-tag, nightly]
    if: needs.parse-tag.outputs.language == 'all'
    uses: ./.github/workflows/mirror-selenium-releases.yml
    secrets: inherit

  on-release-failure:
    name: On Release Failure
    runs-on: ubuntu-latest
    needs: [publish, publish-python, docs, create-tag, github-release-draft, github-release-publish, update-version, reset-dependencies, commit-dependencies, nightly, mirror, verify]
    if: failure()
    steps:
      - uses: actions/checkout@v6
      - name: Slack Notification
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_ICON_EMOJI: ":rotating_light:"
          SLACK_COLOR: failure
          SLACK_CHANNEL: selenium-tlc
          SLACK_USERNAME: GitHub Workflows
          SLACK_TITLE: Release failed
          SLACK_MESSAGE: |
            • Selenium Published: ${{ needs.publish.result }}
            • Python Published: ${{ needs.publish-python.result }}
            • Tag Created: ${{ needs.create-tag.result }}
            • GitHub Release Draft: ${{ needs.github-release-draft.result }}
            • Docs Updated: ${{ needs.docs.result }}
            • GitHub Release Published: ${{ needs.github-release-publish.result }}
            • Nightly Version Updated: ${{ needs.update-version.result }}
            • Nightly Lockfiles Updated: ${{ needs.commit-dependencies.result }}
            • Nightly Packages: ${{ needs.nightly.result }}
            • Mirror Updated: ${{ needs.mirror.result }}
            • Packages Verified: ${{ needs.verify.result }}
          MSG_MINIMAL: actions url
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
