name: Bazel

on:
  workflow_call:
    inputs:
      name:
        description: Name of workflow
        required: false
        type: string
      ref:
        description: Git ref to checkout (branch, tag, or SHA)
        required: false
        type: string
        default: ''
      run:
        description: Bazel command to run
        required: true
        type: string
      os:
        description: One of ubuntu, windows or macos
        required: false
        type: string
        default: ubuntu
      needs-display:
        description: Whether this job drives a browser and needs display/driver setup
        required: false
        type: boolean
        default: false
      node-version:
        description: Custom Node version to install
        required: false
        type: string
        default: ''
      python-version:
        description: Custom Python version to use
        required: false
        type: string
        default: ''
      ruby-version:
        description: Custom Ruby version to use
        required: false
        type: string
        default: ''
      artifact-name:
        description: Name of artifact to upload
        required: false
        type: string
        default: ''
      artifact-path:
        description: Path/glob of files to upload (if empty, uploads git diff as changes.patch)
        required: false
        type: string
        default: ''
      rerun-with-debug:
        description: Rerun failing tests with debugging enabled
        required: false
        type: boolean
        default: false
      gpg-sign:
        description: Import GPG key for signing (Java releases)
        required: false
        type: boolean
        default: false
      fetch-depth:
        description: Number of commits to fetch (0 for full history, empty for auto-detect)
        required: false
        type: string
        default: ''
      cache-name:
        description: Name for cache restore (restores {name}.gz with key {name}-)
        required: false
        type: string
        default: ''
      cache-save:
        description: Save the Bazel cache after the run (avoids multiple jobs clobbering it)
        required: false
        type: boolean
        default: false
      gem-trusted-publishing:
        description: Exchange GitHub OIDC token for a RubyGems API token (requires trusted publisher configured on rubygems.org)
        required: false
        type: boolean
        default: false

jobs:
  bazel:
    name: ${{ inputs.name }}
    runs-on: ${{ contains(inputs.os, '-') && inputs.os || format('{0}-latest', inputs.os) }}
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      SEL_M2_USER: ${{ secrets.SEL_M2_USER }}
      SEL_M2_PASS: ${{ secrets.SEL_M2_PASS }}
      TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
      TWINE_NIGHTLY_PASSWORD: ${{ secrets.TWINE_NIGHTLY_PASSWORD }}
      GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }}
      NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
    steps:
      - name: Configure Selenium Manager cache path
        shell: bash
        run: echo "SE_CACHE_PATH=$RUNNER_TEMP/selenium-cache" >> "$GITHUB_ENV"
      - name: Calculate fetch depth
        id: depth
        shell: bash
        env:
          FETCH_DEPTH: ${{ inputs.fetch-depth }}
          PR_COMMITS: ${{ github.event.pull_request.commits }}
        run: |
          # Use explicit value if provided
          if [ -n "$FETCH_DEPTH" ]; then
            echo "val=$FETCH_DEPTH" >> "$GITHUB_OUTPUT"
          # For PRs, use commit count plus buffer for merge base
          elif [ -n "$PR_COMMITS" ]; then
            echo "val=$((PR_COMMITS + 2))" >> "$GITHUB_OUTPUT"
          # For push events, read commit count from event payload
          else
            COMMIT_COUNT=$(jq -e '.commits | length // 0' "$GITHUB_EVENT_PATH" 2>/dev/null) || COMMIT_COUNT=0
            echo "val=$((COMMIT_COUNT + 1))" >> "$GITHUB_OUTPUT"
          fi
      - name: Checkout source tree
        uses: actions/checkout@v6
        with:
          ref: ${{ inputs.ref || github.ref }}
          fetch-depth: ${{ steps.depth.outputs.val }}
          fetch-tags: ${{ inputs.fetch-depth != '' }}
      - name: Pull latest changes from head ref for PRs
        if: contains(github.head_ref, 'renovate/')
        shell: bash
        run: git pull origin "$HEAD_REF"
        env:
          HEAD_REF: ${{ github.head_ref }}
      - name: Pull latest changes from ref for branch pushes
        if: contains(github.ref, 'renovate/')
        shell: bash
        run: git pull origin "$GIT_REF"
        env:
          GIT_REF: ${{ github.ref }}
      - name: Restore cache
        if: inputs.cache-name != ''
        uses: actions/cache/restore@v5
        with:
          path: ${{ inputs.cache-name }}
          key: ${{ inputs.cache-name }}-
          restore-keys: ${{ inputs.cache-name }}-
      - name: Set Python version
        if: inputs.python-version != ''
        run: echo '${{ inputs.python-version }}' > py/.python-version
      - name: Set Ruby version
        if: inputs.ruby-version != ''
        run: echo '${{ inputs.ruby-version }}' > rb/.ruby-version
      - name: Setup Node
        if: inputs.node-version != ''
        uses: actions/setup-node@v6
        with:
          node-version: ${{ inputs.node-version }}
      - name: Setup Bazel with caching
        continue-on-error: true
        uses: bazel-contrib/setup-bazel@0.19.0
        with:
          cache-save: ${{ inputs.cache-save }}
          bazelisk-cache: true
          bazelrc: common --color=yes
          # Workaround for long path issues: https://github.com/bazelbuild/bazel/pull/22532
          output-base: ${{ inputs.os == 'windows' && 'D://b' || '' }}
          disk-cache: false
          external-cache: false
          repository-cache: true
      - name: Disk status after cache restore
        if: inputs.os != 'macos'
        shell: bash
        run: . ./scripts/github-actions/disk-status.sh
      - name: Setup Fluxbox and Xvfb
        if: inputs.needs-display && inputs.os == 'ubuntu'
        run: |
          sudo apt-get update && sudo apt-get -y install fluxbox x11-utils
          Xvfb :99 &
          # X server must start before fluxbox
          for _ in $(seq 1 30); do
            xdpyinfo -display :99 >/dev/null 2>&1 && break
            sleep 0.5
          done
          xdpyinfo -display :99 >/dev/null 2>&1 || { echo "::error::X server :99 failed to start"; exit 1; }
          fluxbox -display :99 &
          echo "DISPLAY=:99" >> "$GITHUB_ENV"
      - name: Set resolution
        if: inputs.needs-display && inputs.os == 'windows'
        run: Set-DisplayResolution -Width 1920 -Height 1080 -Force
      - name: Disable 8dot3 short names
        if: inputs.os == 'windows'
        run: fsutil 8dot3name set 0
      - name: Setup Safari
        if: inputs.needs-display && inputs.os == 'macos'
        run: sudo safaridriver --enable
      - name: Import GPG key
        if: inputs.gpg-sign
        uses: crazy-max/ghaction-import-gpg@v7
        with:
          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
          passphrase: ${{ secrets.GPG_PASSPHRASE }}
      - name: Configure RubyGems credentials via OIDC
        if: inputs.gem-trusted-publishing
        uses: rubygems/configure-rubygems-credentials@main
      - name: Delete browsers and drivers
        if: inputs.os == 'ubuntu' || inputs.os == 'macos'
        run: ./scripts/github-actions/delete-browsers-drivers.sh
      - name: Delete drivers (Windows)
        if: inputs.os == 'windows'
        shell: pwsh
        run: ./scripts/github-actions/delete-browsers-drivers.ps1
      - name: Free disk space
        if: inputs.os == 'ubuntu'
        shell: bash
        run: |
          . ./scripts/github-actions/disk-status.sh
          if [ "$AVAIL_GB" -lt 20 ]; then
            ./scripts/github-actions/free-disk-space.sh
          fi
      - name: Run Bazel
        id: run-bazel
        continue-on-error: true
        shell: bash
        env:
          MSYS_NO_PATHCONV: 1
          MSYS2_ARG_CONV_EXCL: "*"
        run: |
          mkdir -p build
          {
            ${{ inputs.run }}
          } 2>&1 | tee build/bazel-console.log
      - name: Rerun failures with debug
        id: rerun-failures
        if: steps.run-bazel.outcome == 'failure'
        shell: bash
        run: ./scripts/github-actions/rerun-failures.sh '${{ inputs.run }}' '${{ inputs.rerun-with-debug }}'
      - name: Collect failed test logs
        if: always() && steps.run-bazel.outcome == 'failure'
        shell: bash
        run: ./scripts/github-actions/collect-test-logs.sh
      - name: Upload failed test logs
        if: always() && steps.run-bazel.outcome == 'failure'
        uses: actions/upload-artifact@v7
        with:
          name: test-logs-${{ inputs.os }}-${{ inputs.name }}
          retention-days: 7
          path: build/failures/**
      - name: Start SSH session
        if: failure() && runner.debug == '1'
        uses: mxschmitt/action-tmate@v3
        with:
          limit-access-to-actor: false
      - name: Save git diff
        if: always() && inputs.artifact-name != '' && inputs.artifact-path == ''
        shell: bash
        run: |
          git add -A && git diff --binary --staged > changes.patch
          [ -s changes.patch ] || rm -f changes.patch
      - name: Upload artifact
        if: always() && inputs.artifact-name != ''
        uses: actions/upload-artifact@v7
        with:
          name: ${{ inputs.artifact-name }}
          path: ${{ inputs.artifact-path || 'changes.patch' }}
          retention-days: 6
          if-no-files-found: ${{ inputs.artifact-path != '' && 'error' || 'ignore' }}
      - name: Low Disk Warning
        if: always()
        shell: bash
        run: |
          . ./scripts/github-actions/disk-status.sh
          if [ "$AVAIL_GB" -lt 5 ]; then
            echo "::warning::Low disk space: ${AVAIL_GB}GB remaining"
          fi
