name: Release

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:
    inputs:
      version:
        description: "Version tag (e.g., v1.0.0)"
        required: true
        default: "v1.0.0"

jobs:
  # Get version first (quick job)
  get-version:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
      is_prerelease: ${{ steps.version.outputs.is_prerelease }}
    steps:
      - name: Get version
        id: version
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            VERSION="${{ github.event.inputs.version }}"
          else
            VERSION="${GITHUB_REF#refs/tags/}"
          fi
          echo "version=$VERSION" >> $GITHUB_OUTPUT

          # Check if version contains -preview, -beta, -rc, or -alpha (NOT -hotfix)
          VERSION_LOWER=$(echo "$VERSION" | tr '[:upper:]' '[:lower:]')
          if [[ "$VERSION_LOWER" == *"-preview"* ]] || [[ "$VERSION_LOWER" == *"-beta"* ]] || [[ "$VERSION_LOWER" == *"-rc"* ]] || [[ "$VERSION_LOWER" == *"-alpha"* ]]; then
            echo "is_prerelease=true" >> $GITHUB_OUTPUT
            echo "Detected pre-release version: $VERSION"
          else
            echo "is_prerelease=false" >> $GITHUB_OUTPUT
            echo "Detected stable version: $VERSION"
          fi

  # Android and iOS build in PARALLEL
  build-android:
    runs-on: ubuntu-latest
    needs: get-version

    steps:
      - name: Free disk space
        run: |
          # Remove large unused tools (~15GB total). Docker prune was dropped:
          # it took 1-2 minutes and the rm below already frees enough.
          sudo rm -rf /usr/share/dotnet
          sudo rm -rf /opt/ghc
          sudo rm -rf /opt/hostedtoolcache/CodeQL
          sudo rm -rf /usr/local/share/boost
          sudo rm -rf /usr/share/swift
          sudo rm -rf /usr/local/.ghcup
          # Show available space
          df -h

      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Setup Java
        uses: actions/setup-java@v5
        with:
          distribution: "temurin"
          java-version: "25"

      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version-file: go_backend/go.mod
          cache-dependency-path: go_backend/go.sum

      # Cache Gradle for faster builds
      - name: Cache Gradle
        uses: actions/cache@v5
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: gradle-${{ runner.os }}-

      - name: Cache Android NDK
        uses: actions/cache@v5
        with:
          path: /usr/local/lib/android/sdk/ndk/29.0.14206865
          key: ndk-29.0.14206865

      - name: Install Android SDK & NDK
        run: |
          # Use pre-installed Android SDK on GitHub runners
          echo "ANDROID_HOME=$ANDROID_HOME"
          echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT"

          # Accept licenses
          yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses || true

          # Install NDK r29 (supports 16KB page size for Android 15+)
          # Keep the installed platform aligned with compileSdk/targetSdk.
          $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "ndk;29.0.14206865" "platforms;android-37.0" "build-tools;37.0.0"

          # Set NDK path
          echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/29.0.14206865" >> $GITHUB_ENV

      - name: Install gomobile
        working-directory: go_backend
        run: |
          # Installed from inside the module so the go.mod-pinned x/mobile
          # version is used (reproducible + covered by the setup-go cache).
          go install golang.org/x/mobile/cmd/gomobile
          gomobile init

      - name: Build Go backend for Android
        working-directory: go_backend
        run: |
          mkdir -p ../android/app/libs
          # arm/arm64 only: ndk.abiFilters in app/build.gradle.kts strips
          # every other ABI from all outputs (universal APK included), so an
          # amd64 slice would never reach a shipped APK — it only bloats the
          # aar and slows this step.
          gomobile bind -target=android/arm,android/arm64 -androidapi 24 -o ../android/app/libs/gobackend.aar .
        env:
          CGO_ENABLED: 1

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          channel: "stable"
          flutter-version-file: .fvmrc
          cache: true

      - name: Cache pub dependencies
        uses: actions/cache@v5
        with:
          path: ~/.pub-cache
          key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }}
          restore-keys: pub-${{ runner.os }}-

      - name: Get Flutter dependencies
        run: flutter pub get

      - name: Generate app icons
        run: dart run flutter_launcher_icons

      - name: Build APK (Release - unsigned)
        run: |
          flutter build apk --release --split-per-abi \
            --target-platform android-arm,android-arm64
          ls -la build/app/outputs/flutter-apk/
          test -f build/app/outputs/flutter-apk/app-arm64-v8a-release.apk
          test -f build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk

      - name: Sign APKs
        uses: r0adkll/sign-android-release@v1
        id: sign_arm64
        with:
          releaseDirectory: build/app/outputs/flutter-apk
          signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
          alias: ${{ secrets.KEY_ALIAS }}
          keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
          keyPassword: ${{ secrets.KEY_PASSWORD }}
        env:
          BUILD_TOOLS_VERSION: "36.0.0"

      - name: Rename APKs
        run: |
          VERSION="${{ needs.get-version.outputs.version }}"
          cd build/app/outputs/flutter-apk

          rename_required_apk() {
            unsigned="$1"
            destination="$2"
            signed="${unsigned%.apk}-signed.apk"
            if [ -f "$signed" ]; then
              mv "$signed" "$destination"
            elif [ -f "$unsigned" ]; then
              mv "$unsigned" "$destination"
            else
              echo "ERROR: Missing APK: $unsigned"
              exit 1
            fi
          }

          rename_required_apk \
            app-arm64-v8a-release.apk \
            "SpotiFLAC-${VERSION}-arm64.apk"
          rename_required_apk \
            app-armeabi-v7a-release.apk \
            "SpotiFLAC-${VERSION}-arm32.apk"
          ls -la

      - name: Upload APK artifact
        uses: actions/upload-artifact@v6
        with:
          name: android-apk
          path: build/app/outputs/flutter-apk/SpotiFLAC-*.apk

  build-ios:
    runs-on: macos-15
    needs: get-version # Only depends on version, NOT android build!

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Select Xcode 26.1.1
        run: |
          sudo xcode-select -s /Applications/Xcode_26.1.1.app
          xcodebuild -version

      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version-file: go_backend/go.mod
          cache-dependency-path: go_backend/go.sum

      # Cache CocoaPods
      - name: Cache CocoaPods
        uses: actions/cache@v5
        with:
          path: ios/Pods
          key: pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }}
          restore-keys: pods-${{ runner.os }}-

      - name: Install gomobile
        working-directory: go_backend
        run: |
          # go.mod-pinned x/mobile version (reproducible + cached by setup-go).
          go install golang.org/x/mobile/cmd/gomobile
          gomobile init

      - name: Build Go backend for iOS
        working-directory: go_backend
        run: |
          mkdir -p ../ios/Frameworks
          gomobile bind -target=ios -tags ios -o ../ios/Frameworks/Gobackend.xcframework .
        env:
          CGO_ENABLED: 1

      - name: Verify XCFramework created
        run: |
          ls -la ios/Frameworks/
          ls -la ios/Frameworks/Gobackend.xcframework/ || (echo "ERROR: XCFramework not found!" && exit 1)

      - name: Add XCFramework to Xcode project
        run: |
          # xcodeproj usually ships with the preinstalled CocoaPods; only
          # install it when missing.
          gem list -i xcodeproj >/dev/null || sudo gem install xcodeproj

          # Create Ruby script to add framework
          cat > add_framework.rb << 'EOF'
          require 'xcodeproj'

          project_path = 'ios/Runner.xcodeproj'
          project = Xcodeproj::Project.open(project_path)

          # Get the main target
          target = project.targets.find { |t| t.name == 'Runner' }

          # Get or create Frameworks group
          frameworks_group = project.main_group.find_subpath('Frameworks', true)
          frameworks_group ||= project.main_group.new_group('Frameworks')

          # Add XCFramework reference
          framework_path = 'Frameworks/Gobackend.xcframework'
          framework_ref = frameworks_group.new_file(framework_path, :project)

          # Add to frameworks build phase
          frameworks_build_phase = target.frameworks_build_phase
          frameworks_build_phase.add_file_reference(framework_ref)

          # Add to embed frameworks build phase
          embed_phase = target.build_phases.find { |p| p.is_a?(Xcodeproj::Project::Object::PBXCopyFilesBuildPhase) && p.name == 'Embed Frameworks' }
          if embed_phase
            build_file = embed_phase.add_file_reference(framework_ref)
            build_file.settings = { 'ATTRIBUTES' => ['CodeSignOnCopy', 'RemoveHeadersOnCopy'] }
          end

          project.save
          puts "Successfully added Gobackend.xcframework to Xcode project"
          EOF

          ruby add_framework.rb

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          channel: "stable"
          flutter-version-file: .fvmrc
          cache: true

      - name: Cache pub dependencies
        uses: actions/cache@v5
        with:
          path: ~/.pub-cache
          key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }}
          restore-keys: pub-${{ runner.os }}-

      - name: Get Flutter dependencies
        run: flutter pub get

      - name: Normalize ffmpeg plugin shell scripts (strip CRLF)
        run: |
          find "$HOME/.pub-cache/hosted" -path "*ffmpeg_kit_flutter_new_full*/scripts/*.sh" -type f -print0 |
            while IFS= read -r -d '' f; do
              perl -pi -e 's/\r$//' "$f"
              chmod +x "$f"
              echo "Normalized line endings: $f"
            done

      - name: Generate app icons
        run: dart run flutter_launcher_icons

      - name: Build iOS (unsigned)
        run: |
          # Build Flutter iOS without codesigning
          flutter build ios --release --no-codesign --config-only

          # Use xcodebuild with code signing disabled
          cd ios
          xcodebuild -workspace Runner.xcworkspace \
            -scheme Runner \
            -configuration Release \
            -sdk iphoneos \
            -destination 'generic/platform=iOS' \
            -archivePath build/Runner.xcarchive \
            archive \
            CODE_SIGNING_ALLOWED=NO \
            CODE_SIGNING_REQUIRED=NO \
            CODE_SIGN_IDENTITY="" \
            DEVELOPMENT_TEAM=""

      - name: Create IPA
        run: |
          VERSION=${{ needs.get-version.outputs.version }}
          mkdir -p build/ios/ipa
          cd ios/build/Runner.xcarchive/Products/Applications
          mkdir Payload
          cp -r Runner.app Payload/
          # Use absolute path to avoid relative path issues
          zip -r $GITHUB_WORKSPACE/build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa Payload
          rm -rf Payload

      - name: Verify IPA created
        run: |
          ls -la build/ios/ipa/
          VERSION=${{ needs.get-version.outputs.version }}
          if [ ! -f "build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa" ]; then
            echo "ERROR: IPA not created!"
            exit 1
          fi

      - name: Upload IPA artifact
        uses: actions/upload-artifact@v6
        with:
          name: ios-ipa
          path: build/ios/ipa/SpotiFLAC-*.ipa

  create-release:
    runs-on: ubuntu-latest
    needs: [get-version, build-android, build-ios]
    permissions:
      contents: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0  # Full history needed for git-cliff

      - name: Generate changelog with git-cliff
        id: changelog
        uses: orhun/git-cliff-action@v4
        with:
          config: cliff.toml
          args: --latest --strip header
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OUTPUT: /tmp/changelog.txt

      - name: Show generated changelog
        run: |
          echo "Generated changelog:"
          cat /tmp/changelog.txt

      - name: Download Android APK
        uses: actions/download-artifact@v7
        with:
          name: android-apk
          path: ./release

      - name: Download iOS IPA
        uses: actions/download-artifact@v7
        with:
          name: ios-ipa
          path: ./release

      - name: Prepare release body
        run: |
          VERSION=${{ needs.get-version.outputs.version }}
          REPO_OWNER="${{ github.repository_owner }}"
          REPO_NAME="${{ github.event.repository.name }}"
          CURRENT_REF=$(git rev-list -n 1 "$VERSION" 2>/dev/null || git rev-parse HEAD)
          PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${CURRENT_REF}^" 2>/dev/null || true)

          # Start with git-cliff changelog, but replace its compare footer with a
          # deterministic previous-tag lookup from git.
          sed '/^## [0-9][0-9.[:alpha:]-]*$/d; /^\*\*Full Changelog\*\*/d' /tmp/changelog.txt > /tmp/release_body.txt

          if [ -n "$PREVIOUS_TAG" ]; then
            printf '\n**Full Changelog**: [%s...%s](https://github.com/%s/%s/compare/%s...%s)\n' \
              "$PREVIOUS_TAG" "$VERSION" "$REPO_OWNER" "$REPO_NAME" "$PREVIOUS_TAG" "$VERSION" \
              >> /tmp/release_body.txt
          fi

          # Append download section
          cat >> /tmp/release_body.txt << FOOTER

          ---

          ### Downloads

          #### Android
          - **arm64**: \`SpotiFLAC-${VERSION}-arm64.apk\` (recommended for modern devices)
          - **arm32**: \`SpotiFLAC-${VERSION}-arm32.apk\` (older devices)

          #### iOS
          - **iOS**: \`SpotiFLAC-${VERSION}-ios-unsigned.ipa\` (sideload required)

          ### Installation
          **Android**: Enable "Install from unknown sources" and install the APK
          **iOS**: Use AltStore, Sideloadly, or similar tools to sideload the IPA
          FOOTER

          echo "Release body:"
          cat /tmp/release_body.txt

      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ needs.get-version.outputs.version }}
          name: SpotiFLAC-Mobile ${{ needs.get-version.outputs.version }}
          body_path: /tmp/release_body.txt
          files: ./release/*
          draft: false
          prerelease: ${{ needs.get-version.outputs.is_prerelease == 'true' }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  update-altstore:
    runs-on: ubuntu-latest
    needs: [get-version, build-ios, create-release]
    if: ${{ needs.get-version.outputs.is_prerelease != 'true' }}
    permissions:
      contents: write

    steps:
      - name: Checkout main branch
        uses: actions/checkout@v6
        with:
          ref: main

      - name: Download iOS IPA
        uses: actions/download-artifact@v7
        with:
          name: ios-ipa
          path: ./release

      - name: Update apps.json
        run: |
          VERSION="${{ needs.get-version.outputs.version }}"
          VERSION_NUM="${VERSION#v}"
          DATE=$(date -u +%Y-%m-%d)
          IPA_FILE=$(find ./release -name "*ios*.ipa" | head -1)

          if [ -z "$IPA_FILE" ]; then
            echo "WARNING: IPA file not found, skipping apps.json update"
            exit 0
          fi

          IPA_SIZE=$(stat -c%s "$IPA_FILE" 2>/dev/null || stat -f%z "$IPA_FILE")

          if [ ! -f apps.json ]; then
            echo "WARNING: apps.json not found on main, skipping"
            exit 0
          fi

          jq --arg ver "$VERSION_NUM" \
             --arg date "$DATE" \
             --arg url "https://github.com/${{ github.repository }}/releases/download/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa" \
             --argjson size "$IPA_SIZE" \
             '.apps[0].version = $ver | .apps[0].versionDate = $date | .apps[0].downloadURL = $url | .apps[0].size = $size' \
             apps.json > apps.json.tmp && mv apps.json.tmp apps.json

          echo "Updated apps.json:"
          cat apps.json

      - name: Commit and push
        run: |
          VERSION="${{ needs.get-version.outputs.version }}"
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add apps.json
          git diff --cached --quiet && echo "No changes to commit" || \
            (git commit -m "chore: update AltStore source to ${VERSION}" && git push)

  notify-telegram:
    runs-on: ubuntu-latest
    needs: [get-version, create-release]
    if: ${{ needs.get-version.outputs.is_prerelease != 'true' }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Download Android APK
        uses: actions/download-artifact@v7
        with:
          name: android-apk
          path: ./release

      - name: Download iOS IPA
        uses: actions/download-artifact@v7
        with:
          name: ios-ipa
          path: ./release

      - name: Generate changelog with git-cliff for Telegram
        uses: orhun/git-cliff-action@v4
        with:
          config: cliff.toml
          args: --latest --strip all
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OUTPUT: /tmp/cliff_tg.txt

      - name: Convert changelog for Telegram
        id: changelog
        run: |
          if [ ! -s /tmp/cliff_tg.txt ]; then
            echo "See release notes on GitHub for details." > /tmp/changelog.txt
          else
            # Convert Markdown to Telegram HTML
            CHANGELOG=$(cat /tmp/cliff_tg.txt | \
              sed '/^## [0-9][0-9.[:alpha:]-]*$/d' | \
              sed '/^\*\*Full Changelog\*\*/d' | \
              sed 's/ by \[@[^]]*\](https:\/\/github\.com\/[^)]*)//g' | \
              sed 's/ by @[A-Za-z0-9_-]\+//g' | \
              sed 's/\[#\([0-9]*\)\]([^)]*)/#\1/g' | \
              sed 's/\[@\([^]]*\)\]([^)]*)/@\1/g' | \
              sed 's/&/\&amp;/g' | \
              sed 's/</\&lt;/g' | \
              sed 's/>/\&gt;/g' | \
              sed 's/\*\*\([^*]*\)\*\*/<b>\1<\/b>/g' | \
              sed 's/^### \(.*\)$/<b>\1<\/b>/g' | \
              sed 's/^## \(.*\)$/<b>\1<\/b>/g' | \
              sed 's/^- /• /g')

            # Truncate for Telegram 4096 char limit
            CHANGELOG=$(echo "$CHANGELOG" | head -c 2500 | sed '$d')
            echo "$CHANGELOG" > /tmp/changelog.txt
          fi

          echo "Telegram changelog:"
          cat /tmp/changelog.txt

      - name: Send to Telegram Channel
        env:
          TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          TELEGRAM_CHANNEL_ID: ${{ secrets.TELEGRAM_CHANNEL_ID }}
        run: |
          VERSION=${{ needs.get-version.outputs.version }}
          CHANGELOG=$(cat /tmp/changelog.txt)
          
          # Find APK files
          ARM64_APK=$(find ./release -name "*arm64*.apk" | head -1)
          ARM32_APK=$(find ./release -name "*arm32*.apk" | head -1)
          
          # Prepare message with changelog (HTML format)
          printf '%s\n' \
            "<b>SpotiFLAC Mobile ${VERSION} Released!</b>" \
            "" \
            "<b>What's New:</b>" \
            "${CHANGELOG}" \
            "" \
            "<a href=\"https://github.com/${{ github.repository }}/releases/tag/${VERSION}\">View Release Notes</a>" \
            > /tmp/telegram_message.txt

          MESSAGE=$(cat /tmp/telegram_message.txt)

          # Send message first (using HTML parse mode)
          # Use --data-urlencode for proper encoding of special chars (+, &, etc.)
          # Use || true to ensure file uploads continue even if message fails
          curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
            --data-urlencode "chat_id=${TELEGRAM_CHANNEL_ID}" \
            --data-urlencode "text=${MESSAGE}" \
            --data-urlencode "parse_mode=HTML" \
            --data-urlencode "disable_web_page_preview=true" || true

          # Upload arm64 APK to channel
          if [ -f "$ARM64_APK" ]; then
            echo "Uploading arm64 APK to Telegram..."
            curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \
              -F chat_id="${TELEGRAM_CHANNEL_ID}" \
              -F document=@"${ARM64_APK}" \
              -F caption="SpotiFLAC Mobile ${VERSION} - arm64 (recommended)"
          fi

          # Upload arm32 APK to channel
          if [ -f "$ARM32_APK" ]; then
            echo "Uploading arm32 APK to Telegram..."
            curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \
              -F chat_id="${TELEGRAM_CHANNEL_ID}" \
              -F document=@"${ARM32_APK}" \
              -F caption="SpotiFLAC Mobile ${VERSION} - arm32"
          fi

          # Upload iOS IPA to channel
          IOS_IPA=$(find ./release -name "*ios*.ipa" | head -1)
          if [ -f "$IOS_IPA" ]; then
            echo "Uploading iOS IPA to Telegram..."
            curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \
              -F chat_id="${TELEGRAM_CHANNEL_ID}" \
              -F document=@"${IOS_IPA}" \
              -F caption="SpotiFLAC Mobile ${VERSION} - iOS (unsigned, sideload required)"
          fi

          echo "Telegram notification sent!"
