name: Build

on:
  push:
    branches: ["master"]
  pull_request:
    branches: ["master"]
  release:
    types: ["created"]

permissions:
  contents: "write"

jobs:
  build:
    strategy:
      matrix:
        os:
          - runner: "ubuntu-22.04"
            sys: "linux"
            arch: "x64"
            friendly_name: "Linux (x64)"
            ext: ""
          - runner: "ubuntu-22.04-arm"
            sys: "linux"
            arch: "arm64"
            friendly_name: "Linux (ARM64)"
            ext: ""
          - runner: "macos-14"
            sys: "darwin"
            arch: "arm64"
            friendly_name: "macOS (Apple Silicon)"
            ext: ""
          - runner: "windows-2022"
            sys: "windows"
            arch: "x64"
            friendly_name: "Windows (x64)"
            ext: ".exe"
        build:
          - channel: "release"
            flag: ""
          - channel: "debug"
            flag: "-g"
        linking:
          - mode: "dynamic"
          - mode: "static"
      fail-fast: false

    runs-on: ${{ matrix.os.runner }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Install the latest version of uv
        uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
        with:
          python-version: "3.13"
          cache-dependency-glob: "**/*.py.lock"

      - name: Restore cached libclang
        id: cache-libclang-restore
        if: matrix.linking.mode == 'static'
        uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
        with:
          path: lib/libclang
          key: libclang-${{ matrix.os.runner }}-${{ matrix.os.arch }}-${{ hashFiles('scripts/libclang.json') }}

      - name: Download libclang
        if: matrix.linking.mode == 'static' && steps.cache-libclang-restore.outputs.cache-hit != 'true'
        run: |
          uv run scripts/download.py

      - name: Setup Go
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
        with:
          go-version: 1.24
          cache: true

      - name: Setup Cangjie
        uses: Zxilly/setup-cangjie@91463316b1c359f216c2ac49e35900f9341c1571 # v3.2.0
        with:
          channel: "nightly"
          version: "1.1.0-alpha.20260603010036"

      - name: Install system libclang (dynamic, Linux)
        if: matrix.linking.mode == 'dynamic' && matrix.os.sys == 'linux'
        run: |
          sudo apt-get update
          sudo apt-get install -y libclang-dev

      - name: Install system libclang (dynamic, macOS)
        if: matrix.linking.mode == 'dynamic' && matrix.os.sys == 'darwin'
        run: |
          brew install llvm
          echo "LIBCLANG_PATH=$(brew --prefix llvm)/lib" >> $GITHUB_ENV

      - name: Install OpenSSL on macOS
        if: runner.os == 'macOS'
        run: |
          brew install openssl
          echo "DYLD_LIBRARY_PATH=$(brew --prefix openssl)/lib:$DYLD_LIBRARY_PATH" >> $GITHUB_ENV

      - name: Setup MSYS2
        if: matrix.os.sys == 'windows'
        uses: msys2/setup-msys2@cafece8e6baf9247cf9b1bf95097b0b983cc558d # v2.31.0
        with:
          msystem: CLANG64
          update: true
          install: >-
            mingw-w64-clang-x86_64-clang
          path-type: inherit

      # Skip debug builds on release events
      - name: Build unix ${{ matrix.build.channel }} (${{ matrix.linking.mode }})
        if: matrix.os.sys != 'windows' && !(github.event_name == 'release' && matrix.build.channel == 'debug')
        run: |
          static_flag=""
          if [ "${{ matrix.linking.mode }}" = "static" ]; then
            static_flag="--static"
          fi
          uv run scripts/cjpm.py $static_flag build --cfg_verbose -V ${{ matrix.build.flag }}

      - name: Build windows ${{ matrix.build.channel }} (${{ matrix.linking.mode }})
        if: matrix.os.sys == 'windows' && !(github.event_name == 'release' && matrix.build.channel == 'debug')
        shell: msys2 {0}
        run: |
          static_flag=""
          if [ "${{ matrix.linking.mode }}" = "static" ]; then
            static_flag="--static"
          fi
          uv run python scripts/cjpm.py $static_flag build --cfg_verbose -V ${{ matrix.build.flag }}

      - name: Enable core dumps (Linux)
        if: matrix.os.sys == 'linux'
        run: |
          sudo mkdir -p /tmp/cores
          sudo chmod 1777 /tmp/cores
          sudo sysctl -w kernel.core_pattern=/tmp/cores/core.%e.%p
          # Disable apport if present
          sudo service apport stop 2>/dev/null || true
          echo "Core dumps enabled, pattern: /tmp/cores/core.%e.%p"

      - name: Enable core dumps (macOS)
        if: matrix.os.sys == 'darwin'
        run: |
          sudo mkdir -p /tmp/cores
          sudo chmod 1777 /tmp/cores
          sudo sysctl -w kern.corefile=/tmp/cores/core.%P
          echo "Core dumps enabled, pattern: /tmp/cores/core.%P"

      - name: Test binary (Unix)
        id: test_binary_unix
        if: ${{ matrix.os.sys != 'windows' && !(github.event_name == 'release' && matrix.build.channel == 'debug') }}
        continue-on-error: true
        shell: bash
        run: |
          ulimit -c unlimited
          ./target/${{ matrix.build.channel }}/bin/cjbind_cli${{ matrix.os.ext }} --version

      - name: Test binary (Windows)
        id: test_binary_win
        if: ${{ matrix.os.sys == 'windows' && !(github.event_name == 'release' && matrix.build.channel == 'debug') }}
        continue-on-error: true
        shell: msys2 {0}
        run: |
          ./target/${{ matrix.build.channel }}/bin/cjbind_cli${{ matrix.os.ext }} --version

      - name: Collect crash artifacts (Linux)
        if: (steps.test_binary_unix.outcome == 'failure' || steps.test_binary_win.outcome == 'failure') && matrix.os.sys == 'linux'
        run: |
          mkdir -p crash-artifacts
          cp ./target/${{ matrix.build.channel }}/bin/cjbind_cli crash-artifacts/ || true
          cp /tmp/cores/core.* crash-artifacts/ 2>/dev/null || true
          ls -la /tmp/cores/ 2>/dev/null || echo "No cores in /tmp/cores/"
          ls -la crash-artifacts/

      - name: Collect crash artifacts (macOS)
        if: (steps.test_binary_unix.outcome == 'failure' || steps.test_binary_win.outcome == 'failure') && matrix.os.sys == 'darwin'
        run: |
          mkdir -p crash-artifacts
          cp ./target/${{ matrix.build.channel }}/bin/cjbind_cli crash-artifacts/ || true
          cp /tmp/cores/core.* crash-artifacts/ 2>/dev/null || true
          ls -la /tmp/cores/ 2>/dev/null || echo "No cores in /tmp/cores/"
          # macOS crash reports
          cp ~/Library/Logs/DiagnosticReports/cjbind_cli* crash-artifacts/ 2>/dev/null || true
          ls -la crash-artifacts/

      - name: Collect crash artifacts (Windows)
        if: (steps.test_binary_unix.outcome == 'failure' || steps.test_binary_win.outcome == 'failure') && matrix.os.sys == 'windows'
        shell: bash
        run: |
          mkdir -p crash-artifacts
          cp ./target/${{ matrix.build.channel }}/bin/cjbind_cli.exe crash-artifacts/ || true
          # Windows minidumps
          find /c/Users/runneradmin/AppData/Local/CrashDumps -name '*.dmp' -exec cp {} crash-artifacts/ \; 2>/dev/null || true
          ls -la crash-artifacts/

      - name: Upload crash artifacts
        if: (steps.test_binary_unix.outcome == 'failure' || steps.test_binary_win.outcome == 'failure')
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: crash-${{ matrix.os.runner }}-${{ matrix.os.arch }}-${{ matrix.build.channel }}-${{ matrix.linking.mode }}
          path: crash-artifacts/

      - name: Upload artifact
        if: ${{ !(github.event_name == 'release' && matrix.build.channel == 'debug') }}
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: cjbind-${{ matrix.os.runner }}-${{ matrix.os.arch }}-${{ matrix.build.channel }}-${{ matrix.linking.mode }}
          path: |
            target/${{ matrix.build.channel }}/bin/cjbind_cli*

      - name: Fail if test crashed
        if: (steps.test_binary_unix.outcome == 'failure' || steps.test_binary_win.outcome == 'failure')
        run: exit 1

      - name: Rename binary
        if: github.event_name == 'release' && matrix.build.channel == 'release'
        shell: bash
        run: |
          cd target/release/bin
          mv cjbind_cli${{ matrix.os.ext }} cjbind-${{ matrix.os.sys }}-${{ matrix.os.arch }}-${{ matrix.linking.mode }}${{ matrix.os.ext }}

      - name: Upload to Release
        if: github.event_name == 'release' && matrix.build.channel == 'release'
        shell: bash
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          ASSET_PATH: target/release/bin/cjbind-${{ matrix.os.sys }}-${{ matrix.os.arch }}-${{ matrix.linking.mode }}${{ matrix.os.ext }}
        run: |
          gh release upload ${{ github.event.release.tag_name }} "$ASSET_PATH#${{ matrix.os.friendly_name }} (${{ matrix.linking.mode }})" \
            --clobber

      - name: Always Save libclang
        id: cache-libclang-save
        if: always() && matrix.linking.mode == 'static' && steps.cache-libclang-restore.outputs.cache-hit != 'true'
        uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
        with:
          key: ${{ steps.cache-libclang-restore.outputs.cache-primary-key }}
          path: |
            lib/libclang