on:
  push:
    branches: [ "trunk" ]
  pull_request:
    branches: [ "trunk" ]
  schedule:
    - cron: '0 0 * * 5'  # Scheduled to run every Friday at UTC 00:00

name: Continuous Integration

env:
  CARGO_TERM_COLOR: always

jobs:
  code-file-check:
    if: github.event_name != 'schedule'
    name: Code File Check
    runs-on: ubuntu-latest
    steps:
      - name: Checkout the Repo
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          submodules: recursive
          fetch-depth: 0
      - name: Detect changed directories
        id: changes
        run: |
          if [ "${{ github.event_name }}" = "pull_request" ]; then
            git fetch origin trunk:trunk
            CHANGED_DIRS=$(git diff --name-only trunk...HEAD | while read -r file; do case "$file" in */*) echo "${file%%/*}" ;; *) echo "." ;; esac; done | sort -u | tr '\n' ' ')
            echo "dirs=$CHANGED_DIRS" >> $GITHUB_OUTPUT
            echo "extension_changed=$(echo $CHANGED_DIRS | grep -q 'extension' && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT
            echo "Changed directories: $CHANGED_DIRS"
          else
            echo "dirs=all" >> $GITHUB_OUTPUT
            echo "extension_changed=true" >> $GITHUB_OUTPUT
            echo "Running full lint for non-PR event"
          fi
      - name: Prepare Rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable
          override: true
          components: rustfmt, clippy
      - name: Prepare LLVM lint tools
        if: steps.changes.outputs.extension_changed == 'true'
        run: |
          pip install "clang-format==20.*"
          pip install "clang-tidy==20.*"
      - name: Install required libraries
        if: steps.changes.outputs.extension_changed == 'true'
        run: |
          sudo apt update && sudo apt install -y \
            libgmock-dev \
            libgtest-dev
      - name: Show tools version
        id: rust_ver
        run: |
          echo Rust toolchain: "$(cargo --version)"
          if [ "${{ steps.changes.outputs.extension_changed }}" = "true" ]; then
            echo clang-format: $(clang-format --version)
            echo clang-tidy: $(clang-tidy --version)
          fi
      - name: Lint code files
        run: |
          if [ "${{ steps.changes.outputs.dirs }}" = "all" ]; then
            echo "Running full lint..."
            make lint
          else
            echo "Running directory-specific lint for: ${{ steps.changes.outputs.dirs }}"
            for dir in ${{ steps.changes.outputs.dirs }}; do
              case $dir in
                "src")
                  echo "Linting src/ directory"
                  make lint-src
                  ;;
                "benchmarks")
                  echo "Linting benchmarks/ directory"
                  make lint-benchmarks
                  ;;
                "example")
                  echo "Linting example/ directory"
                  make lint-example
                  ;;
                "extension")
                  echo "Linting extension/ directory"
                  make lint-extension
                  ;;
                *)
                  echo "No linting rules for directory: $dir"
                  ;;
              esac
            done
          fi
      - name: Check lint result
        run: |
          if git diff --quiet; then
            echo "No code changes needed. Proceeding with tests."
          else
            echo "Code formatting changes detected. Please run 'make link' locally for changes."
            echo "Modified files:"
            git diff --name-only
            exit 1
          fi

  test:
    name: Test Suite (${{ matrix.target }})
    runs-on: ${{ matrix.os }}
    needs: code-file-check
    if: always() && (github.event_name == 'schedule' || needs.code-file-check.result == 'success')
    strategy:
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-24.04-arm
          - target: x86_64-unknown-windows-msvc
            os: windows-latest
          - target: aarch64-apple-darwin
            os: macos-latest
    steps:
      - name: Checkout the repo
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          submodules: recursive
      - name: Prepare Rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable
          override: true
          components: rustfmt, clippy
      - name: Rust toolchain version
        uses: actions-rs/cargo@v1
        with:
          command: version
      - name: Install required libraries
        if: startsWith(matrix.os, 'ubuntu')
        run: |
          sudo apt update && sudo apt install -y \
            libgmock-dev \
            libgtest-dev
      - name: Build
        uses: actions-rs/cargo@v1
        with:
          command: build
          args: --verbose --features ci
      - name: Run Test Cases
        uses: actions-rs/cargo@v1
        with:
          command: test
          args: --verbose --features ci
      - name: Build & Test Extension (${{ matrix.target }})
        if: startsWith(matrix.os, 'ubuntu')
        run: |
          echo "Building and testing extension..."
          make build-extension
          make test-extension
      - name: Test benchmarks (${{ matrix.target }})
        run: |
          cd benchmarks
          cargo build
          cargo run -- --num=10 --benchmarks=full
      - name: Test example [simple_app] (${{ matrix.target }})
        run: |
          cd example/simple_app
          cargo build
          cargo run
      - name: Test example [http_server] (${{ matrix.target }})
        if: startsWith(matrix.os, 'ubuntu')
        run: |
          cd example/http_server
          cargo build
          timeout 10s cargo run &
          SERVER_PID=$!
          sleep 3
          chmod +x e2e_test.sh
          ./e2e_test.sh
          TEST_EXIT_CODE=$?
          kill $SERVER_PID
          wait $SERVER_PID
          exit $TEST_EXIT_CODE

