name: ci-rust

on:
  push:
    branches: [main]
    paths:
      - "rs/**"
      - ".github/workflows/ci-rust.yml"
  pull_request:
    branches: [main]
    paths:
      - "rs/**"
      - ".github/workflows/ci-rust.yml"

jobs:
  # Detect which parts of the rs/ tree changed. Adapter-only PRs skip the
  # other platforms; core / workspace-root changes still fan out to all
  # three platform jobs because each one also tests capslockx-core.
  changes:
    name: Detect changed paths
    runs-on: ubuntu-latest
    outputs:
      core: ${{ steps.filter.outputs.core }}
      windows: ${{ steps.filter.outputs.windows }}
      linux: ${{ steps.filter.outputs.linux }}
      macos: ${{ steps.filter.outputs.macos }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            core:
              - 'rs/core/**'
              - 'rs/Cargo.toml'
              - 'rs/Cargo.lock'
              - 'rs/tools/**'
              - '.github/workflows/ci-rust.yml'
            windows:
              - 'rs/adapters/windows/**'
            linux:
              - 'rs/adapters/linux/**'
            macos:
              - 'rs/adapters/macos/**'

  build-and-test:
    name: Build & Test (windows-latest / x86_64-pc-windows-msvc)
    runs-on: windows-latest
    needs: changes
    if: ${{ needs.changes.outputs.core == 'true' || needs.changes.outputs.windows == 'true' }}

    steps:
      - uses: actions/checkout@v4

      # Stable toolchain + clippy.
      # windows-latest ships with the MSVC linker and Windows SDK, so
      # x86_64-pc-windows-msvc is available without any extra setup.
      - name: Install Rust stable
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy

      # Cache ~/.cargo/registry, ~/.cargo/git, and rs/target.
      # workspaces: "<manifest-dir> -> <target-dir>" (non-root workspace)
      - name: Cache Rust build artefacts
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: rs -> rs/target

      # Type-check platform-relevant crates (exclude macOS/linux adapters).
      - name: cargo check
        working-directory: rs
        run: cargo check -p capslockx-core -p capslockx-windows --all-targets

      - name: cargo clippy
        working-directory: rs
        # Temporarily relaxed from `-D warnings` while the WIP voice/agent
        # backlog (committed in af7842a as a snapshot) lints clean. Tighten
        # back once those modules get a focused clippy pass.
        run: cargo clippy -p capslockx-core -p capslockx-windows --all-targets

      - name: cargo test
        working-directory: rs
        run: cargo test -p capslockx-core -p capslockx-windows

      # Full release build to catch LTO / codegen-units issues early.
      - name: cargo build --release (windows adapter)
        working-directory: rs
        run: cargo build -p capslockx-windows --release

  build-linux:
    name: Build & Test (ubuntu-latest / x86_64-unknown-linux-gnu)
    runs-on: ubuntu-latest
    needs: changes
    if: ${{ needs.changes.outputs.core == 'true' || needs.changes.outputs.linux == 'true' }}

    steps:
      - uses: actions/checkout@v4

      - name: Install system dependencies
        run: sudo apt-get update && sudo apt-get install -y libasound2-dev pkg-config

      - name: Install Rust stable
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy

      - name: Cache Rust build artefacts
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: rs -> rs/target

      # Check platform-relevant crates (exclude macOS/windows adapters).
      - name: cargo check
        working-directory: rs
        run: cargo check -p capslockx-core -p capslockx-linux --all-targets

      - name: cargo clippy
        working-directory: rs
        run: cargo clippy -p capslockx-core -p capslockx-linux --all-targets

      - name: cargo test
        working-directory: rs
        run: cargo test -p capslockx-core -p capslockx-linux

      # Full release build of the Linux adapter.
      - name: cargo build --release (linux adapter)
        working-directory: rs
        run: cargo build -p capslockx-linux --release

  build-macos:
    name: Build & Test (macos-latest / aarch64-apple-darwin)
    runs-on: macos-latest
    needs: changes
    if: ${{ needs.changes.outputs.core == 'true' || needs.changes.outputs.macos == 'true' }}

    steps:
      - uses: actions/checkout@v4

      - name: Install Rust stable
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy

      - name: Cache Rust build artefacts
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: rs -> rs/target

      # Check platform-relevant crates (exclude windows/linux adapters).
      - name: cargo check
        working-directory: rs
        run: cargo check -p capslockx-core -p capslockx-macos --all-targets

      - name: cargo clippy
        working-directory: rs
        run: cargo clippy -p capslockx-core -p capslockx-macos --all-targets

      - name: cargo test
        working-directory: rs
        run: cargo test -p capslockx-core -p capslockx-macos

      # Full release build of the macOS adapter.
      - name: cargo build --release (macos adapter)
        working-directory: rs
        run: cargo build -p capslockx-macos --release

  coverage:
    name: Coverage report (capslockx-core, lib only)
    runs-on: ubuntu-latest
    needs: [changes, build-linux]
    if: ${{ needs.changes.outputs.core == 'true' }}

    steps:
      - uses: actions/checkout@v4

      - name: Install system dependencies
        run: sudo apt-get update && sudo apt-get install -y libasound2-dev pkg-config

      - name: Install Rust stable
        uses: dtolnay/rust-toolchain@stable
        with:
          components: llvm-tools-preview

      - name: Cache Rust build artefacts
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: rs -> rs/target

      - name: Install cargo-llvm-cov
        uses: taiki-e/install-action@cargo-llvm-cov

      # Coverage threshold gate. Conservative starting floor — raise as the
      # codebase stabilises. Failing under the threshold blocks the PR.
      # Override via repo variable `COVERAGE_MIN_LINES` if you need to bump
      # without editing this file.
      - name: Generate coverage report (with threshold)
        working-directory: rs
        env:
          COVERAGE_MIN_LINES: ${{ vars.COVERAGE_MIN_LINES || '20' }}
        run: |
          cargo llvm-cov --lib -p capslockx-core \
            --fail-under-lines "$COVERAGE_MIN_LINES" \
            --summary-only | tee coverage.txt

      - name: Generate LCOV report
        if: always()
        working-directory: rs
        run: cargo llvm-cov report --lcov --output-path coverage.lcov

      - name: Upload coverage artefact
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: |
            rs/coverage.txt
            rs/coverage.lcov
