name: CI

on:
  push:
    branches: [master]
  pull_request:
    branches: [master]

concurrency:
  group: ci-${{ github.event.pull_request.number || github.sha }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: 0

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          fetch-depth: 0
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
        with:
          toolchain: 1.92.0
          components: rustfmt, clippy
      - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2

      - name: Ensure web/dist placeholder exists
        run: mkdir -p web/dist && touch web/dist/.gitkeep

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

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

  bench-compile:
    name: Verify Benchmarks Compile
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
        with:
          toolchain: 1.92.0
      - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2

      - name: Ensure web/dist placeholder exists
        run: mkdir -p web/dist && touch web/dist/.gitkeep

      - name: Verify benchmarks compile
        run: cargo bench --no-run --locked

  lint-strict-delta:
    name: Strict Delta Lint
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          fetch-depth: 0
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
        with:
          toolchain: 1.92.0
          components: clippy
      - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2

      - name: Ensure web/dist placeholder exists
        run: mkdir -p web/dist && touch web/dist/.gitkeep

      - name: Run strict delta lint gate
        run: bash scripts/ci/rust_strict_delta_gate.sh
        env:
          BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}

  test:
    name: Test
    runs-on: ubuntu-latest
    timeout-minutes: 30
    needs: [lint]
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
        with:
          toolchain: 1.92.0
      - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2

      - name: Ensure web/dist placeholder exists
        run: mkdir -p web/dist && touch web/dist/.gitkeep

      - name: Install mold linker
        run: |
          sudo apt-get update -qq
          sudo apt-get install -y mold

      - name: Install cargo-nextest
        run: curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin

      - name: Run tests
        run: cargo nextest run --locked
        env:
          CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang
          CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C link-arg=-fuse-ld=mold"

  build:
    name: Build ${{ matrix.target }}
    runs-on: ${{ matrix.os }}
    timeout-minutes: 40
    needs: [lint]
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
          - os: macos-14
            target: aarch64-apple-darwin
          - os: windows-latest
            target: x86_64-pc-windows-msvc
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
        with:
          toolchain: 1.92.0
          targets: ${{ matrix.target }}
      - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
        if: runner.os != 'Windows'

      - name: Install mold linker
        if: runner.os == 'Linux'
        run: |
          sudo apt-get update -qq
          sudo apt-get install -y mold

      - name: Ensure web/dist placeholder exists
        shell: bash
        run: mkdir -p web/dist && touch web/dist/.gitkeep

      - name: Build release
        shell: bash
        run: cargo build --profile ci --locked --target ${{ matrix.target }}
        env:
          CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang
          CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C link-arg=-fuse-ld=mold"

  check-all-features:
    name: Check (all features)
    runs-on: ubuntu-latest
    timeout-minutes: 20
    needs: [lint]
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          fetch-depth: 0
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
        with:
          toolchain: 1.92.0
      - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2

      - name: Install system dependencies
        run: sudo apt-get update -qq && sudo apt-get install -y libudev-dev

      - name: Ensure web/dist placeholder exists
        run: mkdir -p web/dist && touch web/dist/.gitkeep

      - name: Check all features
        run: cargo check --features ci-all --locked

  docs-quality:
    name: Docs Quality
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
        with:
          node-version: 20
      - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
        with:
          python-version: "3.12"

      - name: Run docs quality gate
        run: bash scripts/ci/docs_quality_gate.sh
        env:
          BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}

  # Composite status check — branch protection requires this single job.
  gate:
    name: CI Required Gate
    if: always()
    needs: [lint, bench-compile, lint-strict-delta, test, build, docs-quality, check-all-features]
    runs-on: ubuntu-latest
    steps:
      - name: Check upstream job results
        env:
          HAS_FAILURE: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
        run: |
          if [[ "$HAS_FAILURE" == "true" ]]; then
            echo "::error::One or more upstream jobs failed or were cancelled"
            exit 1
          fi
