name: release

on:
  push:
    tags:
      - 'v*'

permissions:
  contents: write

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        include:
          # Pin to Ubuntu 22.04 (GLIBC 2.35) so the glibc builds run on any
          # distro that's ≥ Ubuntu 22.04 / Debian 12 / Mint 21 / Fedora 36.
          # ubuntu-latest points at 24.04 (GLIBC 2.39) which bakes in a
          # too-new GLIBC symbol requirement and rejects loading on older
          # distros. For users behind tight internet who literally can't
          # dist-upgrade, this matters.
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-22.04
            name: mhrv-rs-linux-amd64
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-22.04
            name: mhrv-rs-linux-arm64
          - target: arm-unknown-linux-gnueabihf
            os: ubuntu-22.04
            name: mhrv-rs-raspbian-armhf
          - target: x86_64-apple-darwin
            os: macos-latest
            name: mhrv-rs-macos-amd64
          - target: aarch64-apple-darwin
            os: macos-latest
            name: mhrv-rs-macos-arm64
          - target: x86_64-pc-windows-gnu
            os: windows-latest
            name: mhrv-rs-windows-amd64
          - target: x86_64-unknown-linux-musl
            os: ubuntu-latest
            name: mhrv-rs-linux-musl-amd64
          - target: aarch64-unknown-linux-musl
            os: ubuntu-latest
            name: mhrv-rs-linux-musl-arm64

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

    steps:
      - uses: actions/checkout@v4

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

      # eframe needs a few system libs on Linux for window management, keyboard,
      # and OpenGL/X11/Wayland. We install them on the Ubuntu runners regardless
      # of arch so both CLI-only and UI builds succeed.
      - name: Install Linux eframe system deps
        if: runner.os == 'Linux'
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            libxkbcommon-dev \
            libwayland-dev \
            libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev \
            libx11-dev \
            libgl1-mesa-dev libglib2.0-dev libgtk-3-dev

      - name: Install aarch64 cross-compile toolchain (Linux only)
        if: matrix.target == 'aarch64-unknown-linux-gnu'
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-aarch64-linux-gnu
          echo '[target.aarch64-unknown-linux-gnu]' >> ~/.cargo/config.toml
          echo 'linker = "aarch64-linux-gnu-gcc"' >> ~/.cargo/config.toml

      - name: Install armhf cross-compile toolchain (Linux only)
        if: matrix.target == 'arm-unknown-linux-gnueabihf'
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-arm-linux-gnueabihf
          echo '[target.arm-unknown-linux-gnueabihf]' >> ~/.cargo/config.toml
          echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config.toml

      - name: Install Windows MinGW toolchain
        if: matrix.target == 'x86_64-pc-windows-gnu'
        id: msys2
        uses: msys2/setup-msys2@v2
        with:
          msystem: MINGW64
          update: true
          install: mingw-w64-x86_64-gcc

      - name: Configure Windows GNU linker
        if: matrix.target == 'x86_64-pc-windows-gnu'
        shell: pwsh
        run: |
          $gcc = "${{ steps.msys2.outputs.msys2-location }}\mingw64\bin\gcc.exe" -replace '\\','/'
          New-Item -ItemType Directory -Force -Path $env:USERPROFILE/.cargo | Out-Null
          Add-Content -Path $env:USERPROFILE/.cargo/config.toml -Value '[target.x86_64-pc-windows-gnu]'
          Add-Content -Path $env:USERPROFILE/.cargo/config.toml -Value "linker = '$gcc'"

      - name: Build CLI
        if: "!endsWith(matrix.target, '-linux-musl')"
        run: cargo build --release --target ${{ matrix.target }} --bin mhrv-rs

      # Fully-static musl builds for OpenWRT / Alpine / libc-less systems.
      # messense/rust-musl-cross ships a pre-built musl toolchain so `ring`
      # (rustls' crypto backend) cross-compiles cleanly on both archs.
      - name: Build CLI (musl via docker)
        if: matrix.target == 'x86_64-unknown-linux-musl'
        run: |
          docker run --rm -v "$PWD":/src -w /src \
            messense/rust-musl-cross:x86_64-musl \
            cargo build --release --target x86_64-unknown-linux-musl --bin mhrv-rs
          sudo chown -R "$(id -u):$(id -g)" target

      - name: Build CLI (musl via docker, arm64)
        if: matrix.target == 'aarch64-unknown-linux-musl'
        run: |
          docker run --rm -v "$PWD":/src -w /src \
            messense/rust-musl-cross:aarch64-musl \
            cargo build --release --target aarch64-unknown-linux-musl --bin mhrv-rs
          sudo chown -R "$(id -u):$(id -g)" target

      # UI build: we try to build the UI binary on every platform. If it fails
      # on cross-compile for linux-arm64 (missing arm64 system libs cross),
      # we still ship the CLI. We also skip the UI on musl targets (OpenWRT etc.
      # are headless, bundling X11 makes no sense).
      - name: Build UI
        if: matrix.target != 'aarch64-unknown-linux-gnu' && matrix.target != 'arm-unknown-linux-gnueabihf' && !endsWith(matrix.target, '-linux-musl')
        run: cargo build --release --target ${{ matrix.target }} --features ui --bin mhrv-rs-ui

      - name: Package (unix)
        if: runner.os != 'Windows'
        run: |
          mkdir -p dist
          cp target/${{ matrix.target }}/release/mhrv-rs dist/mhrv-rs
          chmod +x dist/mhrv-rs
          if [ -f target/${{ matrix.target }}/release/mhrv-rs-ui ]; then
            cp target/${{ matrix.target }}/release/mhrv-rs-ui dist/mhrv-rs-ui
            chmod +x dist/mhrv-rs-ui
          fi
          # OpenWRT / musl archives get the procd init script instead of run.sh,
          # since routers don't have a CA to install and run headless via procd.
          case "${{ matrix.target }}" in
            *-linux-musl)
              cp assets/openwrt/mhrv-rs.init dist/mhrv-rs.init
              chmod +x dist/mhrv-rs.init
              ;;
            *)
              cp assets/launchers/run.sh dist/run.sh
              chmod +x dist/run.sh
              if [ "${{ runner.os }}" = "macOS" ]; then
                cp assets/launchers/run.command dist/run.command
                chmod +x dist/run.command
              fi
              ;;
          esac

      - name: Build macOS .app bundle
        if: runner.os == 'macOS'
        run: |
          VER="${GITHUB_REF#refs/tags/v}"
          ./assets/macos/build-app.sh dist/mhrv-rs-ui "$VER" dist
          # Make a clean zip of just the .app for the release
          cd dist
          zip -qry "${{ matrix.name }}-app.zip" mhrv-rs.app

      - name: Package (windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Force -Path dist | Out-Null
          Copy-Item target/${{ matrix.target }}/release/mhrv-rs.exe dist/mhrv-rs.exe
          if (Test-Path target/${{ matrix.target }}/release/mhrv-rs-ui.exe) {
            Copy-Item target/${{ matrix.target }}/release/mhrv-rs-ui.exe dist/mhrv-rs-ui.exe
          }
          Copy-Item assets/launchers/run.bat dist/run.bat

      - name: Make archive
        shell: bash
        run: |
          cd dist
          case "${{ matrix.target }}" in
            *-pc-windows-*)
              7z a -tzip "${{ matrix.name }}.zip" mhrv-rs.exe mhrv-rs-ui.exe run.bat
              ;;
            *-apple-darwin)
              tar czf "${{ matrix.name }}.tar.gz" mhrv-rs mhrv-rs-ui run.sh run.command
              ;;
            *-linux-musl)
              tar czf "${{ matrix.name }}.tar.gz" mhrv-rs mhrv-rs.init
              ;;
            *)
              tar czf "${{ matrix.name }}.tar.gz" mhrv-rs mhrv-rs-ui run.sh 2>/dev/null || tar czf "${{ matrix.name }}.tar.gz" mhrv-rs run.sh
              ;;
          esac

      - uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.name }}
          path: |
            dist/${{ matrix.name }}.tar.gz
            dist/${{ matrix.name }}.zip
            dist/${{ matrix.name }}-app.zip
          if-no-files-found: ignore

  # Android build — separate job so it doesn't inflate the matrix. The
  # Rust side here cross-compiles to FOUR ABIs (arm64-v8a, armeabi-v7a,
  # x86_64, x86) via cargo-ndk and drops the .so files into the Gradle
  # project's jniLibs/ tree, which then packages them into a single
  # universal APK. Users pick it once, no per-ABI split.
  android:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17

      - name: Set up Android SDK
        uses: android-actions/setup-android@v3
        with:
          cmdline-tools-version: 11076708

      - name: Install NDK
        run: |
          yes | sdkmanager --install "ndk;26.1.10909125" >/dev/null
          echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125" >> "$GITHUB_ENV"

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android,i686-linux-android

      - name: Install cargo-ndk
        run: cargo install cargo-ndk --locked

      # `./gradlew :app:assembleRelease` triggers cargoBuildRelease first
      # which invokes cargo-ndk with all four targets, then Gradle packages
      # the APK (release buildType signed with the debug keystore — see
      # android/app/build.gradle.kts comment explaining why).
      - name: Build release APK
        working-directory: android
        run: |
          chmod +x ./gradlew
          ./gradlew :app:assembleRelease --no-daemon --stacktrace

      - name: Rename APK with version
        working-directory: android
        run: |
          VER="${GITHUB_REF#refs/tags/v}"
          SRC="app/build/outputs/apk/release/app-release.apk"
          if [ ! -f "$SRC" ]; then
            # Some AGP versions name it differently when the release config
            # can't be auto-signed. Catch that up front with a clear error
            # instead of a silent missing-artifact later.
            echo "::error::expected $SRC to exist; actual outputs:"
            find app/build/outputs/apk -type f -name '*.apk' -print
            exit 1
          fi
          mkdir -p ../dist
          cp "$SRC" "../dist/mhrv-rs-android-universal-v${VER}.apk"

      - uses: actions/upload-artifact@v4
        with:
          name: mhrv-rs-android-universal
          path: dist/*.apk
          if-no-files-found: error

  release:
    needs: [build, android]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/download-artifact@v4
        with:
          path: dist
          merge-multiple: true

      - name: Release
        uses: softprops/action-gh-release@v2
        with:
          files: dist/*
          generate_release_notes: true
