name: PR build (required check)

# Monorepo port of the app repos' pr-build.yml: ONE workflow, ONE stable
# required-check name (`gate`), any number of domains. A `changes` job diffs
# the PR against its base; per-domain jobs run only for domains the PR
# touches; `gate` reduces their results into the single status branch
# protection requires.
#
# Deliberately NOT path-filtered at the workflow level: a path-filtered
# required check that never fires leaves the PR stuck "expected -- waiting for
# status" forever, so auto-merge never proceeds. An always-running gate whose
# domain jobs skip cleanly gives the same compute savings without the
# deadlock.
#
# One-time web UI setup (Settings -> Branches, rules for `dev` and `main`):
#   -> enable "Enable Status Check", list this workflow's "gate" job
#   -> merge style: squash
#   Then on a ready PR: "Auto merge when checks succeed".

on:
  pull_request:
    branches: [dev, main]

concurrency:
  group: pr-build-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  changes:
    runs-on: docker
    outputs:
      backend: ${{ steps.diff.outputs.backend }}
      frontend: ${{ steps.diff.outputs.frontend }}
      observability: ${{ steps.diff.outputs.observability }}
    steps:
      - uses: actions/checkout@v4
        with:
          # The PR base commit isn't reachable from a depth-1 clone; full
          # history keeps the diff computable however far the branch diverged.
          fetch-depth: 0

      - name: Detect changed domains
        id: diff
        run: |
          set -euo pipefail
          base="${{ github.event.pull_request.base.sha }}"
          changed=$(git diff --name-only "$base" "$GITHUB_SHA")
          echo "changed files:"; printf '%s\n' "$changed"
          for d in backend frontend observability; do
            if printf '%s\n' "$changed" | grep -q "^$d/"; then
              echo "$d=true" >> "$GITHUB_OUTPUT"
            else
              echo "$d=false" >> "$GITHUB_OUTPUT"
            fi
          done

  build-backend:
    needs: changes
    if: needs.changes.outputs.backend == 'true'
    uses: ./.forgejo/workflows/build.yml
    with:
      domain: backend

  build-frontend:
    needs: changes
    if: needs.changes.outputs.frontend == 'true'
    uses: ./.forgejo/workflows/build.yml
    with:
      domain: frontend

  validate-observability:
    needs: changes
    if: needs.changes.outputs.observability == 'true'
    uses: ./.forgejo/workflows/observability-validate.yml

  gate:
    name: gate
    # always(): skipped domain jobs (nothing changed there) must not skip the
    # gate itself -- the required check has to report on EVERY PR.
    if: always()
    needs: [changes, build-backend, build-frontend, validate-observability]
    runs-on: docker
    steps:
      - name: Reduce domain results
        run: |
          set -euo pipefail
          ok=1
          for pair in \
            "backend=${{ needs.build-backend.result }}" \
            "frontend=${{ needs.build-frontend.result }}" \
            "observability=${{ needs.validate-observability.result }}"; do
            echo "$pair"
            case "${pair#*=}" in
              success|skipped) ;;
              *) ok=0 ;;
            esac
          done
          [ "$ok" = 1 ] || { echo "a changed domain's check failed"; exit 1; }
