name: CI

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

jobs:
  version-sync:
    name: Version Sync Check
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: .node-version

      - name: Check version sync
        run: node scripts/check-version-sync.js

  rust:
    name: Rust
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - name: Cache Rust build artifacts
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: cli

      - name: Format check
        run: cargo fmt --manifest-path cli/Cargo.toml -- --check

      - name: Clippy check
        run: cargo clippy --manifest-path cli/Cargo.toml -- -D warnings

      - name: Run Rust tests
        run: cargo test --profile ci --manifest-path cli/Cargo.toml

  rust-cross:
    name: Rust (${{ matrix.os }} - ${{ matrix.target }})
    if: github.event_name != 'pull_request'
    runs-on: ${{ matrix.os }}
    # Fail fast on a hung test instead of running to GitHub's 6h default.
    timeout-minutes: 30
    strategy:
      matrix:
        include:
          - os: macos-latest
            target: aarch64-apple-darwin
          - os: macos-latest
            target: x86_64-apple-darwin
          - os: windows-latest
            target: x86_64-pc-windows-msvc

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Cache Rust build artifacts
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: cli

      - name: Run Rust tests
        run: cargo test --profile ci --manifest-path cli/Cargo.toml --target ${{ matrix.target }}

  native-e2e:
    name: Native E2E Tests
    if: github.event_name != 'pull_request'
    runs-on: ubuntu-latest
    needs: rust
    # Fail fast on a hung e2e test instead of GitHub's 6h default.
    timeout-minutes: 30
    # This fork forbids headless by default (always-headed for stealth), but CI
    # runners have no display. Opt into the documented display-less escape so
    # launched Chrome can start; e2e tests exercise functionality, not stealth.
    env:
      AGENT_BROWSER_ALLOW_HEADLESS: "1"
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Rust build artifacts
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: cli

      - name: Install Chrome
        run: |
          cargo run --manifest-path cli/Cargo.toml -- install --with-deps

      - name: Install ffmpeg
        run: sudo apt-get update && sudo apt-get install -y ffmpeg

      - name: Run e2e tests
        run: cargo test --profile ci --manifest-path cli/Cargo.toml e2e -- --ignored --test-threads=1

  windows-integration:
    name: Windows Integration Test
    if: github.event_name != 'pull_request'
    runs-on: windows-latest
    needs: rust-cross
    # Headless-forbidden fork on a headless CI runner — opt into the escape so
    # `agent-browser open` can launch Chrome.
    env:
      AGENT_BROWSER_ALLOW_HEADLESS: "1"

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: x86_64-pc-windows-msvc

      - name: Cache Rust build artifacts
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: cli

      - name: Build Rust CLI
        run: cargo build --release --manifest-path cli/Cargo.toml --target x86_64-pc-windows-msvc

      - name: Copy CLI binary to bin directory
        run: |
          Copy-Item cli/target/x86_64-pc-windows-msvc/release/agent-browser.exe bin/agent-browser-win32-x64.exe

      - name: Test agent-browser install command
        run: |
          $env:PATH = "$pwd\bin;$env:PATH"
          for ($i = 1; $i -le 3; $i++) {
            bin/agent-browser-win32-x64.exe install
            if ($LASTEXITCODE -eq 0) { exit 0 }
            Write-Host "Attempt $i failed, retrying in 10 seconds..."
            Start-Sleep -Seconds 10
          }
          exit 1
        shell: pwsh
        timeout-minutes: 10

      - name: Test daemon lifecycle (open, snapshot, close)
        run: |
          $env:PATH = "$pwd\bin;$env:PATH"
          Write-Host "--- Opening page ---"
          # --launch: spawn a standalone browser. Without it, `open` defaults to
          # auto-connect and looks for an existing Chrome on a debug port — which
          # a fresh CI runner doesn't have, so it errors "Could not connect".
          bin/agent-browser-win32-x64.exe --launch open https://example.com
          if ($LASTEXITCODE -ne 0) { Write-Error "open failed"; exit 1 }
          Write-Host "--- Taking snapshot ---"
          $snapshot = bin/agent-browser-win32-x64.exe snapshot
          if ($LASTEXITCODE -ne 0) { Write-Error "snapshot failed"; exit 1 }
          Write-Host $snapshot
          Write-Host "--- Closing browser ---"
          bin/agent-browser-win32-x64.exe close
          if ($LASTEXITCODE -ne 0) { Write-Error "close failed"; exit 1 }
          Write-Host "--- Windows daemon lifecycle test passed ---"
        shell: pwsh
        timeout-minutes: 5

  global-install:
    name: Global Install (${{ matrix.os }})
    if: github.event_name != 'pull_request'
    runs-on: ${{ matrix.os }}
    needs: rust-cross
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            binary: agent-browser-linux-x64
          - os: macos-latest
            target: aarch64-apple-darwin
            binary: agent-browser-darwin-arm64
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            binary: agent-browser-win32-x64.exe

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: .node-version

      - name: Setup Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Cache Rust build artifacts
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: cli

      - name: Build Rust CLI
        run: cargo build --release --manifest-path cli/Cargo.toml --target ${{ matrix.target }}

      - name: Copy CLI binary to bin directory (Unix)
        if: runner.os != 'Windows'
        run: cp cli/target/${{ matrix.target }}/release/agent-browser bin/${{ matrix.binary }}

      - name: Copy CLI binary to bin directory (Windows)
        if: runner.os == 'Windows'
        run: Copy-Item cli/target/${{ matrix.target }}/release/agent-browser.exe bin/${{ matrix.binary }}

      - name: Test npm global install
        run: |
          npm pack
          npm install -g agent-browser-*.tgz
          agent-browser --version
        shell: bash

      - name: Verify symlink points to native binary (Unix)
        if: runner.os != 'Windows'
        run: |
          SYMLINK=$(npm prefix -g)/bin/agent-browser
          TARGET=$(readlink "$SYMLINK")
          echo "Symlink: $SYMLINK"
          echo "Target: $TARGET"
          if [[ "$TARGET" != *"${{ matrix.binary }}"* ]]; then
            echo "ERROR: Symlink should point to native binary, not JS wrapper"
            exit 1
          fi
          echo "Symlink correctly points to native binary"
        shell: bash

      - name: Verify CLI works (and prefers the native shim) (Windows)
        if: runner.os == 'Windows'
        run: |
          # The CLI must work. The native-shim rewrite is a best-effort speedup
          # (npm often creates the .cmd AFTER postinstall runs, so the rewrite
          # can't happen and the JS wrapper — which spawns the native binary — is
          # the valid fallback). Require functionality; prefer, but don't require,
          # the native shim.
          $ver = agent-browser --version
          if ($LASTEXITCODE -ne 0) { Write-Error "agent-browser --version failed"; exit 1 }
          echo "CLI version: $ver"
          $content = Get-Content "$(npm prefix -g)\agent-browser.cmd" -Raw
          echo "Shim content:"
          echo $content
          if ($content -match "agent-browser-win32-x64\.exe") {
            echo "OK: shim points directly to the native binary (zero overhead)"
          } else {
            echo "INFO: shim uses the JS wrapper fallback (functional; native-shim optimization not applied)"
          }
        shell: pwsh
