name: Release Preparation

on:
  workflow_dispatch:
    inputs:
      tag:
        description: 'Release tag (e.g., selenium-4.28.0 or selenium-4.28.1-ruby)'
        required: true
      chrome_channel:
        description: 'Chrome Channel for CDP'
        required: true
        type: choice
        default: "stable"
        options:
          - stable
          - early-stable

permissions:
  contents: read

jobs:
  parse-tag:
    name: Parse Tag
    uses: ./.github/workflows/parse-release-tag.yml
    with:
      tag: ${{ inputs.tag }}

  generate-rust-version:
    name: Generate Rust Version
    needs: [parse-tag, restrict-trunk]
    if: github.event.repository.fork == false && needs.parse-tag.outputs.language == 'all'
    uses: ./.github/workflows/bazel.yml
    with:
      name: Update Rust Version
      run: ./go rust:version ${{ needs.parse-tag.outputs.version }}
      artifact-name: rust-version

  push-rust-version:
    name: Push Rust Version
    needs: [parse-tag, generate-rust-version]
    permissions:
      contents: write
      actions: read
    uses: ./.github/workflows/commit-changes.yml
    with:
      artifact-name: rust-version
      commit-message: "update selenium manager version and rust changelog"
      push-branch: rust-release-${{ needs.parse-tag.outputs.version }}
    secrets:
      SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}

  selenium-manager:
    name: Release Selenium Manager
    needs: [parse-tag, push-rust-version]
    uses: ./.github/workflows/ci-rust.yml
    with:
      release: true
      branch: rust-release-${{ needs.parse-tag.outputs.version }}
    secrets:
      SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}

  cleanup-rust-branch:
    name: Cleanup Rust Branch
    needs: [parse-tag, selenium-manager]
    if: always()
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v6
        with:
          token: ${{ secrets.SELENIUM_CI_TOKEN }}
      - name: Delete rust release branch
        run: |
          git push origin --delete rust-release-${{ needs.parse-tag.outputs.version }} || true

  restrict-trunk:
    name: Restrict Trunk Branch
    needs: parse-tag
    uses: ./.github/workflows/restrict-trunk.yml
    with:
      restrict: true
      message: |
        Trunk branch has been restricted for release preparation (${{ inputs.tag }}).
        To unlock due to release issues, run the "Unlock Trunk" workflow:
        https://github.com/${{ github.repository }}/actions/workflows/unlock-trunk.yml
    secrets:
      SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

  update-versions:
    name: Bump Versions
    needs: [parse-tag, restrict-trunk, selenium-manager]
    if: >-
      always() &&
      needs.restrict-trunk.result == 'success' &&
      (needs.parse-tag.outputs.language != 'all' || needs.selenium-manager.result == 'success')
    uses: ./.github/workflows/bazel.yml
    with:
      name: Bump versions
      run: ./go ${{ needs.parse-tag.outputs.language }}:version ${{ needs.parse-tag.outputs.version }}${{ needs.parse-tag.outputs.language == 'all' && format(' && ./go rust:version {0}', needs.parse-tag.outputs.version) || '' }}
      artifact-name: patch-versions

  commit-versions:
    name: Commit Versions
    needs: [parse-tag, update-versions]
    permissions:
      contents: write
      actions: read
    uses: ./.github/workflows/commit-changes.yml
    with:
      artifact-name: patch-versions
      commit-message: "bump versions"
      push-branch: staging/release-${{ needs.parse-tag.outputs.tag }}
    secrets:
      SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}

  update-devtools:
    name: Update Devtools
    needs: [parse-tag, commit-versions]
    if: needs.parse-tag.outputs.language == 'all'
    uses: ./.github/workflows/bazel.yml
    with:
      name: Update devtools
      ref: staging/release-${{ needs.parse-tag.outputs.tag }}
      run: ./go update_cdp ${{ inputs.chrome_channel }}
      artifact-name: patch-devtools

  commit-devtools:
    name: Commit Devtools
    needs: [parse-tag, update-devtools]
    permissions:
      contents: write
      actions: read
    uses: ./.github/workflows/commit-changes.yml
    with:
      artifact-name: patch-devtools
      commit-message: "update devtools versions"
      ref: staging/release-${{ needs.parse-tag.outputs.tag }}
      push-branch: staging/release-${{ needs.parse-tag.outputs.tag }}
    secrets:
      SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}

  update-dependencies:
    name: Run Dependency Updates
    needs: [parse-tag, commit-versions, commit-devtools]
    if: >-
      always() &&
      needs.commit-versions.result == 'success' &&
      (needs.parse-tag.outputs.language != 'all' || needs.commit-devtools.result == 'success')
    uses: ./.github/workflows/bazel.yml
    with:
      name: Run dependency updates
      ref: staging/release-${{ needs.parse-tag.outputs.tag }}
      run: ./go ${{ needs.parse-tag.outputs.language }}:update${{ needs.parse-tag.outputs.language == 'all' && ' && ./go rust:update' || '' }}
      artifact-name: patch-dep-updates

  calculate-changelog-depth:
    name: Calculate Changelog Depth
    needs: [parse-tag, commit-versions, commit-devtools, update-dependencies]
    if: >-
      always() &&
      needs.commit-versions.result == 'success' &&
      (needs.parse-tag.outputs.language != 'all' || needs.commit-devtools.result == 'success')
    runs-on: ubuntu-latest
    outputs:
      depth: ${{ steps.calc.outputs.depth }}
    steps:
      - name: Calculate fetch depth from tag
        id: calc
        env:
          GH_TOKEN: ${{ github.token }}
          VERSION: ${{ needs.parse-tag.outputs.version }}
          TAG: ${{ needs.parse-tag.outputs.tag }}
        shell: bash
        run: |
          set -euo pipefail
          if [[ "$VERSION" =~ ([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
            MAJOR=${BASH_REMATCH[1]}
            MINOR=${BASH_REMATCH[2]}
            PATCH=${BASH_REMATCH[3]}

            if [ "$PATCH" -gt 1 ]; then
              PREV="selenium-${MAJOR}.${MINOR}.$((PATCH-1))"
            elif [ "$PATCH" -eq 1 ]; then
              PREV="selenium-${MAJOR}.${MINOR}.0"
            elif [ "$MINOR" -gt 0 ]; then
              PREV="selenium-${MAJOR}.$((MINOR-1)).0"
            else
              # First release of major version (X.0.0), use full history
              echo "depth=0" >> "$GITHUB_OUTPUT"
              exit 0
            fi

            COUNT=$(gh api "repos/${{ github.repository }}/compare/${PREV}...staging/release-${TAG}" --jq '.total_commits' 2>/dev/null) || {
              echo "::warning::Failed to get commit count, using full history"
              echo "depth=0" >> "$GITHUB_OUTPUT"
              exit 0
            }
            echo "depth=$((COUNT + 1))" >> "$GITHUB_OUTPUT"
          else
            echo "depth=0" >> "$GITHUB_OUTPUT"
          fi

  update-changelogs:
    name: Generate Changelogs
    needs: [parse-tag, calculate-changelog-depth]
    uses: ./.github/workflows/bazel.yml
    with:
      name: Update changelogs
      ref: staging/release-${{ needs.parse-tag.outputs.tag }}
      run: ./go ${{ needs.parse-tag.outputs.language }}:changelogs
      artifact-name: patch-changelogs
      fetch-depth: ${{ needs.calculate-changelog-depth.outputs.depth }}

  release-updates:
    name: Update ${{ matrix.name }}
    needs: [parse-tag, commit-devtools, selenium-manager, calculate-changelog-depth]
    strategy:
      fail-fast: false
      matrix:
        include:
          - task: browsers
            name: Pinned Browsers
            run: ./go update_browsers
          - task: manager
            name: Selenium Manager Binaries
            run: ./go update_manager
          - task: multitool
            name: Multitool
            run: ./go update_multitool
          - task: cddl
            name: CDDL Spec Files
            run: ./go update_cddl
          - task: authors
            name: Authors
            run: ./go authors
          - task: rust-changelogs
            name: Rust Changelogs
            run: ./go rust:changelogs
    uses: ./.github/workflows/bazel.yml
    with:
      name: Update ${{ matrix.name }}
      ref: staging/release-${{ needs.parse-tag.outputs.tag }}
      run: ${{ matrix.run }}${{ matrix.task == 'browsers' && format(' {0}', inputs.chrome_channel) || '' }}
      artifact-name: patch-${{ matrix.task }}
      fetch-depth: ${{ needs.calculate-changelog-depth.outputs.depth }}

  create-pr:
    name: Create Pull Request
    needs: [parse-tag, update-dependencies, update-changelogs, release-updates]
    if: >-
      always() && !cancelled() &&
      needs.update-dependencies.result == 'success' &&
      needs.update-changelogs.result == 'success' &&
      needs.release-updates.result != 'failure'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout trunk
        uses: actions/checkout@v6
        with:
          token: ${{ secrets.SELENIUM_CI_TOKEN }}
      - name: Download all patches
        uses: actions/download-artifact@v8
        with:
          pattern: patch-*
          path: patches
      - name: Prep git
        run: |
          git config --local user.email "selenium-ci@users.noreply.github.com"
          git config --local user.name "Selenium CI Bot"
      - name: Apply patches and commit
        id: apply
        run: |
          apply_patch() {
            local name="$1"
            local msg="$2"
            local patch="patches/patch-$name/changes.patch"
            if [ -f "$patch" ] && [ -s "$patch" ]; then
              git apply --index "$patch"
              git commit -m "$msg"
              echo "${name}=true" >> "$GITHUB_OUTPUT"
            else
              echo "${name}=false" >> "$GITHUB_OUTPUT"
            fi
          }
          apply_patch versions "bump versions"
          apply_patch devtools "update devtools versions"
          apply_patch cddl "update cddl spec files"
          apply_patch dep-updates "update dependencies"
          apply_patch changelogs "changelogs updated"
          apply_patch rust-changelogs "rust changelogs updated"
          apply_patch browsers "update pinned browser versions"
          apply_patch manager "update selenium manager versions"
          apply_patch multitool "update multitool binaries"
          apply_patch authors "update authors file"
          rm -rf patches
      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v8
        with:
          token: ${{ secrets.SELENIUM_CI_TOKEN }}
          author: Selenium CI Bot <selenium-ci@users.noreply.github.com>
          delete-branch: true
          branch: release-preparation-${{ needs.parse-tag.outputs.tag }}
          base: trunk
          title: "[build] Prepare for release of ${{ needs.parse-tag.outputs.tag }}"
          body: |

            ### Release Info
            | | |
            |-----------|--------|
            | **Tag** | ${{ needs.parse-tag.outputs.tag }} |
            | **Version** | ${{ needs.parse-tag.outputs.version }} |
            | **Language** | ${{ needs.parse-tag.outputs.language }} |

            ### Updates Applied
            | Component | Status |
            |-----------|--------|
            | Versions | ${{ steps.apply.outputs.versions == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)' }} |
            | CDP version | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.devtools == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} |
            | CDDL spec files | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.cddl == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} |
            | Binding Dependencies | ${{ steps.apply.outputs.dep-updates == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)' }} |
            | Changelogs | ${{ steps.apply.outputs.changelogs == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)' }} |
            | Rust Changelogs | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.rust-changelogs == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} |
            | Pinned Browsers | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.browsers == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} |
            | Selenium Manager | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.manager == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} |
            | Multitool binaries | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.multitool == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} |
            | Authors | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.authors == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} |

            ---
            Auto-generated by [release-preparation workflow](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
          labels: C-build

  cleanup-staging-branch:
    name: Cleanup Staging Branch
    needs: [parse-tag, create-pr]
    if: always()
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v6
        with:
          token: ${{ secrets.SELENIUM_CI_TOKEN }}
      - name: Delete staging branch
        run: |
          git push origin --delete staging/release-${{ needs.parse-tag.outputs.tag }} || true

  unlock-trunk-on-failure:
    name: Unlock Trunk on Failure
    needs: [parse-tag, restrict-trunk, generate-rust-version, push-rust-version, selenium-manager, update-versions, commit-versions, update-devtools, commit-devtools, update-dependencies, calculate-changelog-depth, update-changelogs, release-updates, create-pr]
    if: always() && needs.restrict-trunk.result == 'success' && needs.create-pr.result != 'success'
    uses: ./.github/workflows/restrict-trunk.yml
    with:
      restrict: false
      skip_approval: true
    secrets:
      SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

  on-prerelease-failure:
    name: On Pre-release Failure
    runs-on: ubuntu-latest
    needs: [parse-tag, restrict-trunk, generate-rust-version, push-rust-version, selenium-manager, update-versions, commit-versions, update-devtools, commit-devtools, update-dependencies, calculate-changelog-depth, update-changelogs, release-updates, create-pr]
    if: failure() || cancelled()
    steps:
      - 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 preparation failed: ${{ needs.parse-tag.outputs.tag }}"
          SLACK_MESSAGE: |
            • Selenium Manager: ${{ needs.selenium-manager.result }}
            • Versions: ${{ needs.commit-versions.result }}
            • Devtools: ${{ needs.commit-devtools.result }}
            • Dependencies: ${{ needs.update-dependencies.result }}
            • Changelogs: ${{ needs.update-changelogs.result }}
            • Release Updates: ${{ needs.release-updates.result }}
            • Create PR: ${{ needs.create-pr.result }}
          MSG_MINIMAL: actions url
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
