---
name: CI

on:
  push:
    branches: [main]

  pull_request:
    branches: [main]

  merge_group:

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  RUSTUP_MAX_RETRIES: 10
  CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
  CARGO_PROFILE_DEV_DEBUG: 0

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # Detect which files have changed
  changed-files:
    name: Detect changes
    runs-on: ubuntu-latest
    permissions:
      pull-requests: read
    outputs:
      rust_changed: ${{ steps.changes.outputs.rust_changed }}
      deps_changed: ${{ steps.changes.outputs.deps_changed }}
      ci_changed: ${{ steps.changes.outputs.ci_changed }}
      any_changed: ${{ steps.changes.outputs.any_changed }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get changed files
        id: changed-files
        uses: tj-actions/changed-files@v45
        with:
          files_yaml: |
            rust:
              - 'src/**/*.rs'
              - 'tests/**'
            deps:
              - 'Cargo.toml'
              - 'Cargo.lock'
            ci:
              - '.github/workflows/**'

      - name: Determine changes
        id: changes
        env:
          RUST_CHANGED: ${{ steps.changed-files.outputs.rust_any_changed }}
          DEPS_CHANGED: ${{ steps.changed-files.outputs.deps_any_changed }}
          CI_CHANGED: ${{ steps.changed-files.outputs.ci_any_changed }}
        run: |
          # On initial commits, tj-actions/changed-files produces empty
          # outputs because there is no previous commit to diff against.
          # Treat empty values as "true" so all jobs run.
          [ -z "$RUST_CHANGED" ] && RUST_CHANGED="true"
          [ -z "$DEPS_CHANGED" ] && DEPS_CHANGED="true"
          [ -z "$CI_CHANGED" ] && CI_CHANGED="true"

          echo "Rust code changed: $RUST_CHANGED"
          echo "Dependencies changed: $DEPS_CHANGED"
          echo "CI changed: $CI_CHANGED"

          {
            echo "rust_changed=$RUST_CHANGED"
            echo "deps_changed=$DEPS_CHANGED"
            echo "ci_changed=$CI_CHANGED"

            if [ "$RUST_CHANGED" == "true" ] || [ "$DEPS_CHANGED" == "true" ] || [ "$CI_CHANGED" == "true" ]; then
              echo "any_changed=true"
            else
              echo "any_changed=false"
            fi
          } >> "$GITHUB_OUTPUT"

  # Format, compilation, and lint checks
  quick-checks:
    name: Quick Checks
    needs: changed-files
    if: needs.changed-files.outputs.any_changed == 'true'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: 1.92.0
          components: rustfmt, clippy

      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@v2
        with:
          shared-key: "quick-checks-${{ hashFiles('**/Cargo.lock') }}"
          cache-on-failure: true
          cache-all-crates: true

      - name: Check formatting
        run: cargo fmt -- --check

      - name: Check compilation
        run: cargo check --all-targets

      - name: Clippy
        run: cargo clippy --all-targets

  # Test suite on MSRV and stable
  test:
    name: Test (${{ matrix.rust }})
    needs: [quick-checks, changed-files]
    if: needs.changed-files.outputs.any_changed == 'true'
    strategy:
      fail-fast: false
      matrix:
        include:
          - rust: 1.92.0
          - rust: stable
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install Rust toolchain (${{ matrix.rust }})
        uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: ${{ matrix.rust }}

      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@v2
        with:
          shared-key: "tests-${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }}"
          cache-on-failure: true
          cache-all-crates: true
          save-if: ${{ github.ref == 'refs/heads/main' }}

      - name: Install cargo-nextest
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-nextest

      - name: Run tests
        run: cargo nextest run --all-targets

  # Documentation build
  docs:
    name: Documentation
    needs: changed-files
    if: needs.changed-files.outputs.rust_changed == 'true' || needs.changed-files.outputs.deps_changed == 'true' || needs.changed-files.outputs.ci_changed == 'true'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: 1.92.0

      - name: Cache documentation dependencies
        uses: Swatinem/rust-cache@v2
        with:
          shared-key: "docs-${{ hashFiles('**/Cargo.lock') }}"
          cache-on-failure: true
          cache-all-crates: true

      - name: Build documentation
        run: cargo doc --no-deps
        env:
          RUSTDOCFLAGS: -D warnings

  # Branch protection gate
  ci-success:
    name: CI Success
    if: always()
    needs: [changed-files, quick-checks, test, docs]
    runs-on: ubuntu-latest
    steps:
      - name: Check all jobs
        env:
          RUST_CHANGED: ${{ needs.changed-files.outputs.rust_changed }}
          DEPS_CHANGED: ${{ needs.changed-files.outputs.deps_changed }}
          CI_CHANGED: ${{ needs.changed-files.outputs.ci_changed }}
          ANY_CHANGED: ${{ needs.changed-files.outputs.any_changed }}
          QUICK_CHECKS_RESULT: ${{ needs.quick-checks.result }}
          TEST_RESULT: ${{ needs.test.result }}
          DOCS_RESULT: ${{ needs.docs.result }}
        run: |
          echo "Change detection:"
          echo "  Rust changed: $RUST_CHANGED"
          echo "  Deps changed: $DEPS_CHANGED"
          echo "  CI changed: $CI_CHANGED"
          echo "  Any changed: $ANY_CHANGED"
          echo ""
          echo "Job results:"
          echo "  quick-checks: $QUICK_CHECKS_RESULT"
          echo "  test: $TEST_RESULT"
          echo "  docs: $DOCS_RESULT"

          # Check for failures (skipped jobs are OK)
          failed=false

          if [[ "$QUICK_CHECKS_RESULT" == "failure" ]]; then
            echo "quick-checks failed"
            failed=true
          fi

          if [[ "$TEST_RESULT" == "failure" ]]; then
            echo "test failed"
            failed=true
          fi

          if [[ "$DOCS_RESULT" == "failure" ]]; then
            echo "docs failed"
            failed=true
          fi

          if [[ "$failed" == "true" ]]; then
            echo "One or more jobs failed"
            exit 1
          fi

          echo "All required jobs succeeded (or were skipped)"
