name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always
  RUSTFLAGS: -Dwarnings

jobs:
  test:
    name: Test
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 90
    steps:
      - uses: actions/checkout@v4 # v4
        with:
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
      - name: Ensure toolchain is active
        shell: bash
        run: |
          CARGO_HOME="${CARGO_HOME:-$HOME/.cargo}"
          export PATH="$CARGO_HOME/bin:$PATH"
          rustup default stable
          cargo --version
          echo "$CARGO_HOME/bin" >> $GITHUB_PATH
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        with:
          workspaces: rust -> target

      # ---- Windows: MSYS2 + GNU target + jemalloc -------------------------
      # The default MSVC toolchain can't build jemalloc (autotools mismatch).
      # We switch to the GNU target with MSYS2/MinGW and pre-build jemalloc.
      # See .github/actions/windows-jemalloc for the gory details.
      - name: Setup MSYS2 and MinGW
        if: runner.os == 'Windows'
        uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2
        with:
          msystem: MINGW64
          update: true
          install: base-devel mingw-w64-x86_64-toolchain
          path-type: inherit

      - name: Install Rust GNU target
        if: runner.os == 'Windows'
        uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
        with:
          targets: x86_64-pc-windows-gnu

      - name: Build jemalloc + dummy liballoc.a (Windows only)
        if: runner.os == 'Windows'
        id: jemalloc
        uses: ./.github/actions/windows-jemalloc

      - name: Free disk space
        if: runner.os == 'Linux'
        shell: bash
        run: |
          sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL || true
          df -h /
      - name: Run tests
        working-directory: rust
        shell: bash
        run: |
          if [[ "${{ runner.os }}" == "macOS" ]]; then
            CARGO="${CARGO_HOME}/bin/cargo"
          else
            CARGO="cargo"
          fi
          if [[ "${{ runner.os }}" == "Windows" ]]; then
            JEMALLOC_OVERRIDE="${{ steps.jemalloc.outputs.jemalloc-override }}" \
            CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS="-L ${{ steps.jemalloc.outputs.dummy-dir }}" \
            "$CARGO" test --target x86_64-pc-windows-gnu --all-features -- --test-threads=1
          else
            "$CARGO" test --all-features -- --test-threads=1
          fi

  clippy:
    name: Clippy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4 # v4
        with:
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
        with:
          components: clippy
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        with:
          workspaces: rust -> target
      - name: Run clippy
        working-directory: rust
        run: cargo clippy --all-features -- -D warnings

  fmt:
    name: Format
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4 # v4
        with:
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
        with:
          components: rustfmt
      - name: Check formatting
        working-directory: rust
        run: cargo fmt --check

  cookbook:
    name: Cookbook (Node)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4 # v4
        with:
          persist-credentials: false
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
          cache-dependency-path: cookbook/package-lock.json
      - name: Install
        working-directory: cookbook
        run: npm ci
      - name: Lint
        working-directory: cookbook
        run: npm run lint
      - name: Typecheck
        working-directory: cookbook
        run: npm run typecheck
      - name: Test
        working-directory: cookbook
        run: npm test
      - name: Build
        working-directory: cookbook
        run: npm run build

  coverage:
    name: Coverage
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4 # v4
        with:
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        with:
          workspaces: rust -> target
      - name: Free disk space
        run: |
          sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL || true
          df -h /
      - name: Install cargo-tarpaulin
        run: cargo install cargo-tarpaulin
      - name: Generate coverage
        working-directory: rust
        env:
          CARGO_BUILD_JOBS: "2"
        run: |
          cargo tarpaulin --out xml --skip-clean --all-features --lib --timeout 180 \
            --exclude-files 'src/cli/*' --exclude-files 'src/main.rs' \
            -- --test-threads=1
          pkill -f 'target.*lean-ctx' 2>/dev/null || true
      - name: Upload to Codecov
        uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
        with:
          fail_ci_if_error: false

  deny:
    name: cargo-deny
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4 # v4
        with:
          persist-credentials: false
      - uses: EmbarkStudios/cargo-deny-action@5bb39ff5d5a0e94dc9e2dc94eced0c6129743a57 # v2
        with:
          manifest-path: rust/Cargo.toml

  adversarial:
    name: Adversarial Safety
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4 # v4
        with:
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        with:
          workspaces: rust -> target
      - name: Run adversarial compression tests
        working-directory: rust
        run: cargo test --test adversarial_compression -- --nocapture

  bench:
    name: Benchmarks
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        with:
          workspaces: rust -> target
      - name: Compile benchmarks
        working-directory: rust
        run: cargo bench --no-run

  doc:
    name: Documentation
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4 # v4
        with:
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        with:
          workspaces: rust -> target
      - name: Check docs compile
        working-directory: rust
        env:
          RUSTDOCFLAGS: -D warnings
        run: cargo doc --no-deps --all-features

  # Security audit runs in security-check.yml workflow
