name: Nix CI

on:
  push:
    branches: [ main, master ]
  pull_request:
    branches: [ main, master ]
  workflow_dispatch:

jobs:
  # Detect which workspaces have changed to save CI minutes
  filter:
    runs-on: ubuntu-latest
    outputs:
      workspaces: ${{ steps.detect.outputs.workspaces }}
      build_targets: ${{ steps.detect.outputs.build_targets }}
      any_changed: ${{ steps.detect.outputs.any_changed }}
      build_changed: ${{ steps.detect.outputs.build_changed }}
      global_changed: ${{ steps.detect.outputs.global_changed }}
      shared_crates: ${{ steps.detect.outputs.shared_crates }}
      shared_crates_changed: ${{ steps.detect.outputs.shared_crates_changed }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Collect changed files
        env:
          EVENT_NAME: ${{ github.event_name }}
          BASE_REF: ${{ github.base_ref }}
          BEFORE_SHA: ${{ github.event.before }}
          HEAD_SHA: ${{ github.sha }}
        run: |
          set -euo pipefail

          if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
            printf 'flake.nix\n' > changed-files.txt
          elif [[ "$EVENT_NAME" == "pull_request" ]]; then
            git fetch --no-tags --depth=1 origin "$BASE_REF"
            git diff --name-only "origin/$BASE_REF...$HEAD_SHA" > changed-files.txt
          elif [[ "$BEFORE_SHA" == "0000000000000000000000000000000000000000" ]]; then
            git diff-tree --no-commit-id --name-only -r "$HEAD_SHA" > changed-files.txt
          else
            git diff --name-only "$BEFORE_SHA" "$HEAD_SHA" > changed-files.txt
          fi

          if [[ ! -f changed-files.txt ]]; then
            : > changed-files.txt
          fi

          sed -n '1,200p' changed-files.txt

      - name: Detect changed workspaces
        id: detect
        run: |
          python3 scripts/ci_changed_workspaces.py \
            --config nix/ci/workspaces.json \
            --changed-files-file changed-files.txt \
            --github-output "$GITHUB_OUTPUT"

  # Run CI gates for changed workspaces
  # Uses the provider-agnostic 'ultracloud-gate' defined in nix/ci/flake.nix
  gate:
    needs: filter
    if: ${{ needs.filter.outputs.any_changed == 'true' }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        workspace: ${{ fromJSON(needs.filter.outputs.workspaces) }}
    name: gate (${{ matrix.workspace }})
    steps:
      - uses: actions/checkout@v4
      - uses: DeterminateSystems/nix-installer-action@v11
      - uses: DeterminateSystems/magic-nix-cache-action@v8

      - name: Run UltraCloud Gate
        run: |
          nix run ./nix/ci#gate-ci -- --workspace ${{ matrix.workspace }} --tier 0 --no-logs

  shared-crates-gate:
    needs: filter
    if: ${{ needs.filter.outputs.shared_crates_changed == 'true' }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        crate: ${{ fromJSON(needs.filter.outputs.shared_crates) }}
    name: gate (shared crate: ${{ matrix.crate }})
    steps:
      - uses: actions/checkout@v4
      - uses: DeterminateSystems/nix-installer-action@v11
      - uses: DeterminateSystems/magic-nix-cache-action@v8

      - name: Run Shared Crate Gate
        run: |
          nix run ./nix/ci#gate-ci -- --shared-crate ${{ matrix.crate }} --tier 0 --no-logs

  portable-regressions:
    needs: filter
    if: ${{ needs.filter.outputs.any_changed == 'true' || needs.filter.outputs.global_changed == 'true' || needs.filter.outputs.shared_crates_changed == 'true' }}
    runs-on: ubuntu-latest
    name: portable regressions
    steps:
      - uses: actions/checkout@v4
      - uses: DeterminateSystems/nix-installer-action@v11
      - uses: DeterminateSystems/magic-nix-cache-action@v8

      - name: Run portable canonical profile regressions
        run: |
          nix build \
            .#checks.x86_64-linux.canonical-profile-eval-guards \
            .#checks.x86_64-linux.portable-control-plane-regressions \
            --accept-flake-config

  # Build server packages (tier 1+)
  build:
    needs: [filter, gate]
    if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') && needs.filter.outputs.build_changed == 'true' }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target: ${{ fromJSON(needs.filter.outputs.build_targets) }}
    name: build (${{ matrix.target.package }})
    steps:
      - uses: actions/checkout@v4
      - uses: DeterminateSystems/nix-installer-action@v11
      - uses: DeterminateSystems/magic-nix-cache-action@v8

      - name: Build package
        run: |
          nix build .#${{ matrix.target.package }} --accept-flake-config

  # Summary job for PR status checks
  ci-status:
    needs: [filter, gate, shared-crates-gate, portable-regressions]
    if: always()
    runs-on: ubuntu-latest
    steps:
      - name: Check CI Status
        run: |
          if [[ "${{ needs.gate.result }}" == "failure" ]]; then
            exit 1
          fi
          if [[ "${{ needs.shared-crates-gate.result }}" == "failure" ]]; then
            exit 1
          fi
          if [[ "${{ needs.portable-regressions.result }}" == "failure" ]]; then
            exit 1
          fi
          if [[ "${{ needs.filter.outputs.any_changed }}" == "true" || "${{ needs.filter.outputs.global_changed }}" == "true" || "${{ needs.filter.outputs.shared_crates_changed }}" == "true" ]]; then
            if [[ "${{ needs.gate.result }}" == "skipped" ]]; then
              echo "Gate was skipped despite changes. This is unexpected."
              exit 1
            fi
            if [[ "${{ needs.portable-regressions.result }}" == "skipped" ]]; then
              echo "Portable regressions were skipped despite changes. This is unexpected."
              exit 1
            fi
          fi
          if [[ "${{ needs.filter.outputs.shared_crates_changed }}" == "true" ]]; then
            if [[ "${{ needs.shared-crates-gate.result }}" == "skipped" ]]; then
              echo "Shared crates gate was skipped despite crates/** changes. This is unexpected."
              exit 1
            fi
          fi
          echo "CI passed or was correctly skipped."
