name: Lint

on:
  workflow_call:

jobs:
  lint:
    name: Lint (${{ matrix.os }})
    runs-on: ${{ matrix.os }}-latest
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu, macos]
    env:
      # Reduce GC frequency (default 100) to speed up builds/tests at cost of higher memory
      GOGC: "400"

    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
      with:
        fetch-depth: 0

    - name: Set up mise
      uses: jdx/mise-action@c1ecc8f748cd28cdeabf76dab3cccde4ce692fe4 # v4.0.0
      with:
        version: 2026.1.9
        experimental: true
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

    - id: go-cache-paths
      shell: bash
      run: |
        echo "go-build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT"
        echo "go-mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT"

        if [ "$RUNNER_OS" == "macOS" ]; then
          echo "golangci-lint-cache=$HOME/Library/Caches/golangci-lint" >> "$GITHUB_OUTPUT"
          exit 0
        fi

        echo "golangci-lint-cache=$HOME/.cache/golangci-lint" >> "$GITHUB_OUTPUT"

    # Ensure Go cache parent directories exist before cache restore.
    # On macOS runners ~/Library/Caches may not exist on a fresh image,
    # causing actions/cache tar extraction or subsequent mkdir -p to fail.
    - name: Ensure cache directories (macOS)
      if: runner.os == 'macOS'
      run: |
        mkdir -p "$(go env GOCACHE)"
        mkdir -p "$HOME/Library/Caches/golangci-lint"

    - name: Go Build Cache
      uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
      with:
        path: ${{ steps.go-cache-paths.outputs.go-build }}
        key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}-${{ matrix.os }}-${{ runner.arch }}
        restore-keys: |
          ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}-${{ matrix.os }}-

    - name: Go Mod Cache
      uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
      with:
        path: ${{ steps.go-cache-paths.outputs.go-mod }}
        key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}-${{ matrix.os }}-${{ runner.arch }}
        restore-keys: |
          ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}-${{ matrix.os }}-
          ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}-
          ${{ runner.os }}-go-mod-

    - name: golangci-lint Cache
      uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
      with:
        path: ${{ steps.go-cache-paths.outputs.golangci-lint-cache }}
        key: ${{ runner.os }}-golangci-lint-${{ hashFiles('**/go.sum') }}-${{ matrix.os }}-${{ runner.arch }}
        restore-keys: |
          ${{ runner.os }}-golangci-lint-${{ hashFiles('**/go.sum') }}-${{ matrix.os }}-
          ${{ runner.os }}-golangci-lint-

    # A corrupted cache entry may restore go-build or its parent as a
    # regular file instead of a directory. Remove any such files and
    # recreate them as directories so the Go toolchain can use the cache.
    - name: Fix Go build cache dir (macOS)
      if: runner.os == 'macOS'
      run: |
        cache_dir="$(go env GOCACHE)"
        for p in "$(dirname "$cache_dir")" "$cache_dir"; do
          if [ -f "$p" ]; then rm -f "$p"; fi
        done
        mkdir -p "$cache_dir"

    # fetch-depth: 0 fetches current branch history; origin/main ref must be fetched separately.
    # Use refspec main:main to create a local branch (required for git merge-base main HEAD).
    - name: Fetch main branch
      if: startsWith(github.ref, 'refs/heads/') && github.ref != 'refs/heads/main'
      run: git fetch origin main:main

    # macOS runners have ~14GB RAM. GOGC=400 lets the Go heap grow to ~31GB (measured), causing
    # heavy swap and lint timeout. Cap heap to keep golangci-lint within available physical memory.
    - name: Set memory limits (macOS)
      if: runner.os == 'macOS'
      run: |
        echo "GOGC=100" >> "$GITHUB_ENV"
        echo "GOMEMLIMIT=10GiB" >> "$GITHUB_ENV"

    # Linux runners have ~28GB RAM but GOGC=400 still causes the heap to grow beyond available
    # physical memory when linting with many build tags, triggering runner shutdown signals.
    # Cap the heap to keep golangci-lint stable on Linux.
    - name: Set memory limits (Linux)
      if: runner.os == 'Linux'
      run: |
        echo "GOGC=100" >> "$GITHUB_ENV"
        echo "GOMEMLIMIT=20GiB" >> "$GITHUB_ENV"

    - name: Check for lint config changes
      id: lint-config
      if: startsWith(github.ref, 'refs/heads/') && github.ref != 'refs/heads/main'
      run: |
        if git diff --name-only origin/main...HEAD | grep -q '^\.golangci\.yml$'; then
          echo "changed=true" >> "$GITHUB_OUTPUT"
        else
          echo "changed=false" >> "$GITHUB_OUTPUT"
        fi

    - name: Lint
      run: |
        # Full lint on main, tags, or when lint config changed
        if [[ "${{ github.ref }}" != refs/heads/* ]] || \
           [[ "${{ github.ref }}" == "refs/heads/main" ]] || \
           [[ "${{ steps.lint-config.outputs.changed }}" == "true" ]]; then
          make run-lint
        else
          make run-lint-incremental
        fi
