name: Build

on:
  push:
    tags: ['v*']

permissions:
  contents: write

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.26'
          cache: true
      - name: Cache Go build
        uses: actions/cache@v4
        with:
          path: ~/.cache/go-build
          key: gobuild-test-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
          restore-keys: gobuild-test-${{ runner.os }}-
      - name: Test + vet
        run: |
          go vet ./... &
          go test -race -count=1 ./...
          wait

  build:
    needs: test
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - goos: linux
            goarch: amd64
          - goos: linux
            goarch: arm64
          - goos: darwin
            goarch: amd64
          - goos: darwin
            goarch: arm64
          - goos: freebsd
            goarch: amd64
          - goos: freebsd
            goarch: arm64
          - goos: windows
            goarch: amd64
          - goos: android
            goarch: arm64
          - goos: android
            goarch: arm
            goarm: '7'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.26'
          cache: true
      - name: Cache Go build
        uses: actions/cache@v4
        with:
          path: ~/.cache/go-build
          key: gobuild-${{ matrix.goos }}-${{ matrix.goarch }}-${{ hashFiles('**/go.sum') }}
          restore-keys: gobuild-${{ matrix.goos }}-${{ matrix.goarch }}-

      - name: Install UPX
        if: matrix.goos == 'linux' || matrix.goos == 'windows'
        run: sudo apt-get install -y upx-ucl

      - name: Build Server
        if: matrix.goos != 'android'
        env:
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
          CGO_ENABLED: '0'
        run: |
          VERSION=${GITHUB_REF_NAME:-dev}
          COMMIT=$(git rev-parse --short HEAD)
          DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
          LDFLAGS="-s -w -X github.com/sartoopjj/thefeed/internal/version.Version=${VERSION} -X github.com/sartoopjj/thefeed/internal/version.Commit=${COMMIT} -X github.com/sartoopjj/thefeed/internal/version.Date=${DATE}"
          ext=""
          if [ "${{ matrix.goos }}" = "windows" ]; then ext=".exe"; fi
          go build -trimpath -ldflags="${LDFLAGS}" -o build/thefeed-server-${{ matrix.goos }}-${{ matrix.goarch }}${ext} ./cmd/server

      - name: Build Client
        env:
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
          GOARM: ${{ matrix.goarm || '' }}
          CGO_ENABLED: '0'
        run: |
          VERSION=${GITHUB_REF_NAME:-dev}
          COMMIT=$(git rev-parse --short HEAD)
          DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
          ext=""
          BUILD_MODE=""
          if [ "${{ matrix.goos }}" = "windows" ]; then ext=".exe"; fi
          # AssetTemplate matches the published filename in the
          # thefeed-files repo so the in-app update prompt links straight
          # to the right binary. {V} is replaced at runtime with the
          # version string read from the public VERSION file.
          if [ "${{ matrix.goos }}" = "android" ]; then
            ASSET_TEMPLATE="thefeed-client-android-${{ matrix.goarch }}"
          else
            ASSET_TEMPLATE="thefeed-client-{V}-${{ matrix.goos }}-${{ matrix.goarch }}${ext}"
          fi
          LDFLAGS="-s -w -X github.com/sartoopjj/thefeed/internal/version.Version=${VERSION} -X github.com/sartoopjj/thefeed/internal/version.Commit=${COMMIT} -X github.com/sartoopjj/thefeed/internal/version.Date=${DATE} -X github.com/sartoopjj/thefeed/internal/version.AssetTemplate=${ASSET_TEMPLATE}"
          # Modern Android requires PIE for executables launched via exec(),
          # and several heuristic AV engines (Kaspersky Boogr.gsh,
          # several VT vendors) flag non-PIE bundled binaries as suspicious.
          # Force PIE for the binaries that ship inside the APK.
          if [ "${{ matrix.goos }}" = "android" ]; then
            BUILD_MODE="-buildmode=pie"
          fi
          # Android: build with cgo so DNS resolution goes through bionic
          # libc / netd instead of Go's pure-Go resolver, which on Android
          # finds /etc/resolv.conf empty and dies with "[::1]:53 connection
          # refused". Wire the right NDK clang per architecture.
          if [ "${{ matrix.goos }}" = "android" ]; then
            export CGO_ENABLED=1
            case "${{ matrix.goarch }}" in
              arm)
                export CC="$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang"
                ;;
              arm64)
                export CC="$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
                ;;
            esac
            test -x "$CC" || { echo "NDK clang not found at $CC"; exit 1; }
          fi
          if [ "${{ matrix.goos }}" = "android" ] && [ "${{ matrix.goarch }}" = "arm64" ]; then
            out="build/thefeed-client-android-arm64"
          elif [ "${{ matrix.goos }}" = "android" ] && [ "${{ matrix.goarch }}" = "arm" ]; then
            out="build/thefeed-client-android-arm"
          else
            out="build/thefeed-client-${VERSION}-${{ matrix.goos }}-${{ matrix.goarch }}${ext}"
          fi
          go build -trimpath -buildvcs=false $BUILD_MODE -ldflags="${LDFLAGS}" -o "$out" ./cmd/client

      - name: Compress with UPX
        if: matrix.goos == 'linux' || matrix.goos == 'windows'
        run: |
          # Keep best/lzma for small binaries; xargs -P does them
          # in parallel across CPUs so wall time stays low.
          find build -maxdepth 1 -type f -print0 \
            | xargs -0 -n1 -P "$(nproc)" -I{} sh -c 'upx --best --lzma "$1" || true' _ {}

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
          path: build/

  android-apk:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts
          merge-multiple: true

      - name: Stage Android client binary as JNI library
        run: |
          mkdir -p android/app/src/main/jniLibs/arm64-v8a
          mkdir -p android/app/src/main/jniLibs/armeabi-v7a
          test -f artifacts/thefeed-client-android-arm64
          test -f artifacts/thefeed-client-android-arm
          cp artifacts/thefeed-client-android-arm64 android/app/src/main/jniLibs/arm64-v8a/libthefeed.so
          cp artifacts/thefeed-client-android-arm android/app/src/main/jniLibs/armeabi-v7a/libthefeed.so

      - name: Decode signing keystore
        env:
          KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
        run: |
          if [ -n "$KEYSTORE_BASE64" ]; then
            echo "$KEYSTORE_BASE64" | base64 -d > android/app/keystore.jks
            echo "Keystore decoded successfully"
          else
            echo "No KEYSTORE_BASE64 secret set — will use debug signing"
          fi

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

      - name: Set up Gradle
        uses: gradle/actions/setup-gradle@v4

      - name: Build Android APK
        working-directory: android
        env:
          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
        run: |
          VERSION=${GITHUB_REF_NAME:-dev}
          if [ ! -x ./gradlew ]; then gradle wrapper --gradle-version 8.10.2; fi
          if [ -f app/keystore.jks ]; then BT=release; TASK=assembleRelease; else BT=debug; TASK=assembleDebug; fi

          # No `clean` — fresh checkout is already clean and the
          # Gradle build cache can reuse work between runs.
          ./gradlew --no-daemon --build-cache $TASK

          APK_DIR=app/build/outputs/apk/$BT
          cp "$APK_DIR"/app-arm64-v8a-${BT}.apk    ../artifacts/thefeed-android-${VERSION}-arm64-v8a.apk
          cp "$APK_DIR"/app-armeabi-v7a-${BT}.apk  ../artifacts/thefeed-android-${VERSION}-armeabi-v7a.apk

      - name: Upload Android APK artifacts
        uses: actions/upload-artifact@v4
        with:
          name: thefeed-android-apk
          path: |
            artifacts/thefeed-android-*-arm64-v8a.apk
            artifacts/thefeed-android-*-armeabi-v7a.apk

  ios-ipa:
    needs: test
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.26'
          cache: true

      - name: Select Xcode
        run: sudo xcode-select -s /Applications/Xcode.app

      - name: Install gomobile + gobind
        run: |
          go install golang.org/x/mobile/cmd/gomobile@latest
          go install golang.org/x/mobile/cmd/gobind@latest
          gomobile init
          go get golang.org/x/mobile/bind golang.org/x/mobile/bind/objc
          go mod tidy

      - name: Build Mobile.xcframework
        run: |
          VERSION=${GITHUB_REF_NAME:-dev}
          COMMIT=$(git rev-parse --short HEAD)
          DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
          LDFLAGS="-X github.com/sartoopjj/thefeed/internal/version.Version=${VERSION} -X github.com/sartoopjj/thefeed/internal/version.Commit=${COMMIT} -X github.com/sartoopjj/thefeed/internal/version.Date=${DATE}"
          gomobile bind -iosversion=14.0 -target=ios,iossimulator -ldflags="${LDFLAGS}" -o ios/Mobile.xcframework ./mobile

      - name: Archive (unsigned)
        run: |
          VERSION=${GITHUB_REF_NAME:-dev}
          MARKETING_VERSION=${VERSION#v}
          BUILD_NUMBER=$(git rev-list --count HEAD)
          xcodebuild \
            -project ios/Thefeed.xcodeproj \
            -scheme Thefeed \
            -configuration Release \
            -destination 'generic/platform=iOS' \
            -archivePath build/Thefeed.xcarchive \
            archive \
            MARKETING_VERSION="${MARKETING_VERSION}" \
            CURRENT_PROJECT_VERSION="${BUILD_NUMBER}" \
            CODE_SIGN_IDENTITY="" \
            CODE_SIGNING_REQUIRED=NO \
            CODE_SIGNING_ALLOWED=NO \
            DEVELOPMENT_TEAM=""

      - name: Pack unsigned IPA
        run: |
          VERSION=${GITHUB_REF_NAME:-dev}
          mkdir -p build/Payload
          cp -r build/Thefeed.xcarchive/Products/Applications/Thefeed.app build/Payload/
          (cd build && zip -qry "thefeed-ios-${VERSION}-unsigned.ipa" Payload)
          ls -lh build/*.ipa

      - name: Upload iOS IPA artifact
        uses: actions/upload-artifact@v4
        with:
          name: thefeed-ios-ipa
          path: build/thefeed-ios-*-unsigned.ipa

  release:
    needs: [build, android-apk, ios-ipa]
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    steps:
      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts
          merge-multiple: true

      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          files: artifacts/*
          generate_release_notes: true
          prerelease: ${{ contains(github.ref_name, '-') }}
          append_body: true
          body: |
            ## Downloads

            | Platform | Architecture | Download |
            |----------|-------------|----------|
            | Linux | amd64 | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-linux-amd64) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-linux-amd64) |
            | Linux | arm64 | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-linux-arm64) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-linux-arm64) |
            | macOS | amd64 (Intel) | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-darwin-amd64) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-darwin-amd64) |
            | macOS | arm64 (Apple Silicon) | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-darwin-arm64) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-darwin-arm64) |
            | FreeBSD | amd64 | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-freebsd-amd64) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-freebsd-amd64) |
            | FreeBSD | arm64 | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-freebsd-arm64) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-freebsd-arm64) |
            | Windows | amd64 | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-windows-amd64.exe) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-windows-amd64.exe) |
            | Android | arm64-v8a (most modern phones) | [thefeed-android-${{ github.ref_name }}-arm64-v8a.apk - اندروید (گوشی‌های جدید)](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-android-${{ github.ref_name }}-arm64-v8a.apk) |
            | Android | armeabi-v7a (older 32-bit phones) | [thefeed-android-${{ github.ref_name }}-armeabi-v7a.apk - اندروید (دستگاه‌های قدیمی‌تر)](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-android-${{ github.ref_name }}-armeabi-v7a.apk) |
            | iOS (unsigned) | universal | [thefeed-ios-${{ github.ref_name }}-unsigned.ipa](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-ios-${{ github.ref_name }}-unsigned.ipa) |

            **iOS / iPadOS (preview, iOS 14+):** the `.ipa` is unsigned. Re-sign it with your own Apple ID and provisioning profile (AltStore, Sideloadly, or `xcrun altool` upload to your own TestFlight). It will not install directly from a download.
