name: CI

on:
  push:
    branches: [main]
  pull_request:

env:
  CARGO_TERM_COLOR: always
  RUSTFLAGS: "-D warnings"

jobs:
  fmt:
    name: Format
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt
      - run: cargo fmt --all --check

  clippy:
    name: Lint (${{ matrix.lua }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        # mlua's Lua-flavor features are mutually exclusive, so we can't
        # use --all-features. Run clippy under each supported flavor.
        lua: [luajit, lua54]
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy
      - uses: Swatinem/rust-cache@v2
      - run: cargo clippy --all-targets --no-default-features --features ${{ matrix.lua }} -- -D warnings
      # pmacs-gpu is Lua-flavor-independent (it depends on pmacs-protocol,
      # not pmacs), so the root-package clippy above never lints it. Lint
      # it explicitly or its warnings slip through CI (audit F-001).
      - run: cargo clippy -p pmacs-gpu --all-targets -- -D warnings

  gpu-render:
    name: GPU Render (headless)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      # The headless render harness (audit F-014) needs a Vulkan adapter;
      # the runner has no GPU, so install the mesa software rasterizer
      # (lavapipe). This job also runs pmacs-gpu's other tests, which the
      # workspace test job (root package only) never executes.
      - name: Install lavapipe (Vulkan software rasterizer)
        run: |
          sudo apt-get update
          sudo apt-get install -y mesa-vulkan-drivers vulkan-tools
      - name: Confirm a Vulkan adapter is present
        run: |
          ls -la /usr/share/vulkan/icd.d/ || true
          vulkaninfo --summary || true
      - name: pmacs-gpu tests (render harness included)
        env:
          # Force the Vulkan backend so wgpu uses lavapipe. Do NOT set
          # VK_ICD_FILENAMES — the loader's default ICD discovery already
          # finds lavapipe (vulkaninfo above proves it), and pinning a
          # path that doesn't match the runner hides every ICD. The
          # PMACS_REQUIRE_GPU guard turns a missing adapter into a hard
          # failure so a broken setup can't pass as a silent skip.
          WGPU_BACKEND: vulkan
          LIBGL_ALWAYS_SOFTWARE: "1"
          PMACS_REQUIRE_GPU: "1"
        run: cargo test -p pmacs-gpu -- --test-threads=1

  test:
    name: Test (${{ matrix.os }} / ${{ matrix.lua }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest]
        lua: [luajit, lua54]
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - run: cargo build --all-targets --no-default-features --features ${{ matrix.lua }}
      # Several acceptance binaries spawn real daemon / PTY child
      # processes. Keep the harness serial so macOS runners do not
      # expose cross-test process lifecycle races that are unrelated
      # to the behavior under test.
      - run: cargo test --all-targets --no-default-features --features ${{ matrix.lua }} -- --test-threads=1
      - run: cargo test --doc --no-default-features --features ${{ matrix.lua }}
      # The workspace default member is only the root `pmacs` package, so
      # the runs above never execute pmacs-protocol's own tests — the
      # shared wire format (encode/decode, transport framing) the daemon,
      # TUI, and GPU all depend on. Run them explicitly (Lua-flavor
      # independent, so once is enough; runs on every matrix leg cheaply
      # off the shared cache).
      - run: cargo test -p pmacs-protocol --all-targets

  acceptance:
    name: M1 Acceptance Gates
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: perf, memory, and 30s fuzz gates
        run: cargo test --release --test acceptance -- --ignored --nocapture --test-threads=1

  m4-perf-gates:
    name: M4 Perf Gates
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: tree-sitter parse + highlight latency budgets
        run: cargo test --release --test m4_acceptance -- --ignored --nocapture --test-threads=1

  # m5-perf-gates: luajit-only by design.
  # The keystroke-to-render gate measures protocol-path latency (Unix
  # socket frontend ↔ daemon RTT plus the daemon's keymap-dispatch /
  # buffer-mutate / repaint pipeline). Lua flavor is irrelevant — the
  # measured path doesn't enter the Lua VM. Matrixing over flavors
  # doubles CI cost without adding signal. If a future perf gate
  # measures Lua-touching code paths (command dispatch via Lua,
  # hook invocation), revisit then.
  m5-perf-gates:
    name: M5 Perf Gates
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: keystroke-to-render p99 over loopback LocalSocket
        run: cargo test --release --test m5_perf_acceptance -- --ignored --nocapture

  # m6-perf-gates: luajit-only by design. The M6.6/M6.7 gates measure
  # rope-append throughput, RSS ceiling, cancel-response latency,
  # scrollback navigation latency, and scrollback search latency —
  # none of which exercise the Lua VM's hot path differently across
  # flavors. Same reasoning as m5-perf-gates above.
  #
  # --test-threads=1 is required: the M6.6 RSS-ceiling gate samples
  # process-wide /proc/self/status, and concurrent ingest / scrollback
  # populate from sibling tests inflate that reading. Serializing
  # gives each test a clean baseline. The wall-clock cost of serial
  # execution (~5 minutes) is acceptable for a perf-gates job that
  # only runs on push/PR.
  m6-perf-gates:
    name: M6 Perf Gates
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: ingest rate, RSS ceiling, cancel p99, navigation p99, search p99
        env:
          # Full cancel profile remains the test default. Hosted CI has
          # an effective five-minute ceiling for this job and variable
          # PTY throughput, so keep the per-PR profile short and stable.
          PMACS_M6_INGEST_MIN_BYTES_PER_SEC: "67108864"
          PMACS_M6_CANCEL_TRIALS: "30"
          PMACS_M6_CANCEL_MAX_DELAY_MS: "500"
        run: cargo test --release --test m6_perf_acceptance -- --ignored --nocapture --test-threads=1
