name: CI

on:
  push:
    branches: [main, dev]
  pull_request:
    branches: [main, dev]
  schedule:
    - cron: '0 8 * * 1'  # Weekly Monday 08:00 UTC — smoke tests
  workflow_dispatch:

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

jobs:
  # ── Fast gate: typecheck + build ──
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6
        with:
          node-version: '22'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Type check
        run: npx tsc --noEmit

      - name: Build
        run: npm run build

      # Guard: committed cli-manifest.json must match the one build regenerates.
      # Prevents silent drift where unrelated adapter entries vanish or change
      # across PRs (agent hits unexpected manifest diff → surgical-merge churn).
      - name: Check cli-manifest.json is up-to-date
        if: runner.os == 'Linux'
        shell: bash
        run: |
          if ! git diff --exit-code -- cli-manifest.json; then
            echo "::error::cli-manifest.json is out of sync with the source. Run 'npm run build' and commit the result."
            exit 1
          fi

      # Guard: adapter rows must not silently emit keys omitted from `columns`.
      # Existing findings are tracked in scripts/silent-column-drop-baseline.json;
      # this gate rejects newly introduced drops while allowing incremental cleanup.
      - name: Check silent column drops
        if: runner.os == 'Linux'
        run: npm run check:silent-column-drop

      # Guard: adapters should fail with typed errors instead of silently
      # returning empty arrays, clamping user input, or inventing sentinel data.
      # Existing findings are tracked in scripts/typed-error-lint-baseline.json.
      - name: Check typed-error lint baseline
        if: runner.os == 'Linux'
        run: npm run check:typed-error-lint

  # ── Unit tests (vitest shard) ──
  # PR: ubuntu + Node 22 only (fast feedback, 2 jobs).
  # Push to main/dev: full matrix for cross-platform/cross-version coverage (12 jobs).
  unit-test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && fromJSON('["ubuntu-latest","macos-latest","windows-latest"]') || fromJSON('["ubuntu-latest"]') }}
        node-version: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && fromJSON('["22"]') || fromJSON('["22"]') }}
        shard: [1, 2]
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run unit tests (Node ${{ matrix.node-version }}, shard ${{ matrix.shard }}/2)
        run: npx vitest run --project unit --project extension --reporter=verbose --shard=${{ matrix.shard }}/2

  # ── Bun compatibility check ──
  bun-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - uses: oven-sh/setup-bun@v2
        with:
          bun-version: 1.3.5

      - uses: actions/setup-node@v6
        with:
          node-version: '22'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run unit tests under Bun
        run: bun vitest run --project unit --reporter=verbose

  # Adapter tests are pure unit tests — OS doesn't affect results. Gated off
  # `pull_request` to keep PR CI under ~2 minutes; adapter authors run focused
  # tests locally before pushing, and `push` to main / nightly cron / manual
  # dispatch still guard the merged state.
  adapter-test:
    if: github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6
        with:
          node-version: '22'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run focused adapter tests
        run: npm run test:adapter -- --reporter=verbose

  # ── Smoke tests (scheduled / manual only) ──
  smoke-test:
    if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
    needs: build
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        # NOTE: Windows excluded — browser-actions/setup-chrome hangs during
        # Chrome MSI installation on Windows runners (known issue).
        os: [ubuntu-latest, macos-latest]
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6
        with:
          node-version: '22'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Setup Chrome
        uses: ./.github/actions/setup-chrome
        id: setup-chrome

      - name: Build
        run: npm run build

      - name: Run smoke tests (Linux, via xvfb)
        if: runner.os == 'Linux'
        run: |
          xvfb-run --auto-servernum --server-args="-screen 0 1280x720x24" \
            npx vitest run tests/smoke/ --reporter=verbose

      - name: Run smoke tests (macOS / Windows)
        if: runner.os != 'Linux'
        run: npx vitest run tests/smoke/ --reporter=verbose
    timeout-minutes: 15
