name: Mobile Deploy
# Runs on:
# 1. Manual trigger (workflow_dispatch) with configurable options
# 2. When PRs are merged to staging (auto-deploy to internal track)
# 3. DISABLED: When called by other workflows (workflow_call)
#
# === PR LABELS ===
# - deploy:skip: Skip deployment entirely (no version bump, no builds)
# - version:major/minor/patch: Control version bump type
# - deploy:ios/deploy:android: Build only one platform
#
# === WORKFLOW LOGIC ===
# Build Branch: Always builds from the branch that triggered the workflow
#   - PR merges: Builds from 'staging' branch (the branch being merged into)
#   - Manual dispatch: Builds from the branch where workflow was manually triggered
#   - This allows testing from feature branches before merging to dev/staging
#
# Version Bump PR: After successful build, creates PR to bump version
#   - Target: 'dev' branch
#   - Workflow checks out the target branch, applies version changes, and creates PR
#   - This separates the build source from the version bump destination
#
# Example flows:
#   1. Normal production flow:
#      - Merge PR to staging → builds from staging → creates version bump PR to dev
#   2. Testing from feature branch:
#      - Manually trigger from feature branch → builds from feature branch → creates version bump PR to dev

env:
  # Build environment versions
  RUBY_VERSION: 3.2
  NODE_ENV: "production"
  JAVA_VERSION: 17
  ANDROID_API_LEVEL: 35
  ANDROID_NDK_VERSION: 27.0.12077973
  XCODE_VERSION: 16.4

  # Cache versioning - increment these to bust caches when needed
  GH_CACHE_VERSION: v1 # Global cache version
  GH_YARN_CACHE_VERSION: v1 # Yarn-specific cache version
  GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version
  GH_PODS_CACHE_VERSION: v1 # CocoaPods cache version
  GH_GRADLE_CACHE_VERSION: v1 # Gradle cache version

  # Path configuration
  WORKSPACE: ${{ github.workspace }}
  APP_PATH: ${{ github.workspace }}/app

  # Certificate/keystore paths
  ANDROID_KEYSTORE_PATH: /android/app/upload-keystore.jks
  ANDROID_PLAY_STORE_JSON_KEY_PATH: /android/play-store-key.json
  IOS_DIST_CERT_PATH: /ios/certs/dist_cert.p12
  IOS_CONNECT_API_KEY_PATH: /ios/certs/connect_api_key.p8
  IOS_PROV_PROFILE_PROJ_PATH: /ios/certs/profile.mobileprovision
  IOS_PROV_PROFILE_DIRECTORY: "~/Library/MobileDevice/Provisioning\ Profiles/"

permissions:
  contents: read

on:
  workflow_dispatch:
    inputs:
      platform:
        description: "Select platform to build"
        required: true
        default: "both"
        type: choice
        options:
          - ios
          - android
          - both
      test_mode:
        description: "Test mode (skip upload to stores)"
        required: false
        type: boolean
        default: false
      deployment_track:
        description: "Deployment track (internal/production)"
        required: false
        type: choice
        default: "internal"
        options:
          - internal
          - production
      version_bump:
        description: "Version bump type"
        required: false
        type: choice
        default: "build"
        options:
          - build
          - patch
          - minor
          - major
      dry_run:
        description: "Do not commit/push or create PR/tags"
        required: false
        type: boolean
        default: false

  pull_request:
    types: [closed]
    branches: [staging]
    paths:
      - "app/**"
      - "!app/**/*.md"
      - "!app/docs/**"
      - "packages/mobile-sdk-alpha/**"
      - ".github/workflows/mobile-deploy.yml"

  workflow_call:
    inputs:
      platform:
        type: string
        required: true
      deployment_track:
        type: string
        required: false
        default: "internal"
      version_bump:
        type: string
        required: false
        default: "build"
      auto_deploy:
        type: boolean
        required: false
        default: false
      test_mode:
        type: boolean
        required: false
        default: false

concurrency:
  # Group by deployment track or ref name to allow different tracks to run in parallel
  # cancel-in-progress: false ensures we don't cancel ongoing deployments
  # Branch-locking in create-version-bump-pr prevents duplicate PRs for same version
  group: mobile-deploy-${{ inputs.deployment_track || github.ref_name }}
  cancel-in-progress: false

jobs:
  # Bump version atomically before platform builds to avoid race conditions
  # NOTE: Checks out the triggering branch (staging for PR merges, or the branch where manually triggered)
  bump-version:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    # Guard against auto-closed PRs (GitHub reports merged == false when a PR closes without merging)
    if: |
      (
        github.event_name != 'pull_request' ||
        (github.event.action == 'closed' && github.event.pull_request.merged == true)
      ) &&
      (
        github.event_name != 'pull_request' ||
        !contains(github.event.pull_request.labels.*.name, 'deploy:skip')
      )
    outputs:
      version: ${{ steps.bump.outputs.version }}
      ios_build: ${{ steps.bump.outputs.ios_build }}
      android_build: ${{ steps.bump.outputs.android_build }}
      version_bump_type: ${{ steps.determine-bump.outputs.version_bump }}
      platform: ${{ steps.determine-platform.outputs.platform }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
          # Build from the branch that triggered the workflow (staging, feature branch, etc.)
          ref: ${{ github.ref_name }}
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Determine version bump from PR labels or input
        id: determine-bump
        run: |
          VERSION_BUMP="${{ inputs.version_bump || 'build' }}"

          # Override with PR label if present
          if [ "${{ github.event_name }}" = "pull_request" ]; then
            LABELS='${{ toJSON(github.event.pull_request.labels.*.name) }}'
            if echo "$LABELS" | grep -q "version:major"; then
              VERSION_BUMP="major"
            elif echo "$LABELS" | grep -q "version:minor"; then
              VERSION_BUMP="minor"
            elif echo "$LABELS" | grep -q "version:patch"; then
              VERSION_BUMP="patch"
            fi
          fi

          echo "version_bump=$VERSION_BUMP" >> $GITHUB_OUTPUT
          echo "📦 Version bump type: $VERSION_BUMP"

      - name: Determine platform from labels or input
        id: determine-platform
        run: |
          PLATFORM="both"

          # Check workflow input first
          if [ -n "${{ inputs.platform }}" ]; then
            INPUT_PLATFORM="${{ inputs.platform }}"
            if [ "$INPUT_PLATFORM" = "ios" ]; then
              PLATFORM="ios"
            elif [ "$INPUT_PLATFORM" = "android" ]; then
              PLATFORM="android"
            fi
          fi

          # Override with PR labels if present
          if [ "${{ github.event_name }}" = "pull_request" ]; then
            LABELS='${{ toJSON(github.event.pull_request.labels.*.name) }}'
            if echo "$LABELS" | grep -q "deploy:ios"; then
              PLATFORM="ios"
            elif echo "$LABELS" | grep -q "deploy:android"; then
              PLATFORM="android"
            fi
          fi

          echo "platform=$PLATFORM" >> $GITHUB_OUTPUT
          echo "📱 Platform to deploy: $PLATFORM"

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: .nvmrc

      - name: Bump version using version-manager script
        id: bump
        run: |
          cd ${{ env.APP_PATH }}

          VERSION_BUMP="${{ steps.determine-bump.outputs.version_bump }}"
          PLATFORM="${{ steps.determine-platform.outputs.platform }}"

          echo "🔄 Calculating version bump..."
          echo "  Type: $VERSION_BUMP"
          echo "  Platform: $PLATFORM"
          echo ""

          # Use version-manager script to calculate bump
          # NOTE: Using absolute path to ensure script is found regardless of CWD
          node ${{ env.APP_PATH }}/scripts/version-manager.cjs bump "$VERSION_BUMP" "$PLATFORM"

          echo ""
          echo "✅ Version bump calculated successfully"
          echo "⚠️  Note: Changes are local only. Will be committed in PR after successful builds."

      - name: Verify bump outputs were set
        run: |
          VERSION="${{ steps.bump.outputs.version }}"
          IOS_BUILD="${{ steps.bump.outputs.ios_build }}"
          ANDROID_BUILD="${{ steps.bump.outputs.android_build }}"

          if [ -z "$VERSION" ] || [ -z "$IOS_BUILD" ] || [ -z "$ANDROID_BUILD" ]; then
            echo "❌ Version bump failed to set required outputs"
            echo "version='$VERSION', ios_build='$IOS_BUILD', android_build='$ANDROID_BUILD'"
            exit 1
          fi

          echo "✅ All version outputs verified"

  build-ios:
    needs: [bump-version]
    # runs-on: macos-latest-large
    runs-on: namespace-profile-apple-silicon-6cpu
    permissions:
      contents: read
      actions: write
    if: |
      (github.event_name != 'pull_request' ||
        (github.event.action == 'closed' && github.event.pull_request.merged == true)) &&
      (
        (inputs.platform == 'ios' || inputs.platform == 'both') ||
        (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'deploy:android'))
      )
    steps:
      - name: Mobile deployment status
        run: |
          echo "🚀 Mobile deployment is enabled - proceeding with iOS build"
          echo "📱 Platform: ${{ inputs.platform || 'both' }}"
          echo "🎯 Track: ${{ inputs.deployment_track || 'internal' }}"
          echo "📦 Version bump: ${{ inputs.version_bump || 'build' }}"
          if [ "${{ github.event_name }}" = "pull_request" ]; then
            echo "🔀 Triggered by PR merge: #${{ github.event.pull_request.number }} - ${{ github.event.pull_request.title }}"
            echo "👤 Merged by: ${{ github.event.pull_request.merged_by.login }}"
            if ${{ contains(github.event.pull_request.labels.*.name, 'deploy:ios') }}; then
              echo "🏷️  Label: deploy:ios (skipping Android)"
            fi
          fi

      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
          # Checkout the branch that triggered the workflow
          ref: ${{ github.ref_name }}
      - name: Read and sanitize Node.js version
        shell: bash
        run: |
          if [ ! -f .nvmrc ] || [ -z "$(cat .nvmrc)" ]; then
            echo "❌ .nvmrc is missing or empty"; exit 1;
          fi
          VERSION="$(tr -d '\r\n' < .nvmrc)"
          VERSION="${VERSION#v}"
          if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then
            echo "Invalid .nvmrc content: '$VERSION'"; exit 1;
          fi
          echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV"
          echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV"

      - name: Verify branch and commit (iOS)
        if: needs.bump-version.outputs.platform != 'android'
        run: |
          echo "🔍 Verifying we're building from the correct branch and commit..."
          echo "Current branch: $(git branch --show-current || git symbolic-ref --short HEAD 2>/dev/null || echo 'detached')"
          echo "Current commit: $(git rev-parse HEAD)"
          echo "Current commit message: $(git log -1 --pretty=format:'%s')"
          BUILD_BRANCH="${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || github.ref_name }}"
          echo "Building from branch: $BUILD_BRANCH"
          echo "Target HEAD commit: $(git rev-parse origin/$BUILD_BRANCH)"
          echo "Target HEAD message: $(git log -1 --pretty=format:'%s' origin/$BUILD_BRANCH)"

          if [ "${{ github.event_name }}" = "pull_request" ]; then
            echo "📌 Building from merge commit on staging (includes source + conflict resolutions)"
            echo "PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}"
            echo "Merge commit includes version.json from source branch with bumped build numbers"
          elif [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/$BUILD_BRANCH)" ]; then
            echo "⚠️ WARNING: Current commit differs from latest $BUILD_BRANCH commit"
            echo "This might indicate we're not building from the latest $BUILD_BRANCH branch"
            git log --oneline HEAD..origin/$BUILD_BRANCH || true
          else
            echo "✅ Building from latest $BUILD_BRANCH commit"
          fi

      - name: Apply version bump for build
        if: needs.bump-version.outputs.platform != 'android'
        run: |
          cd ${{ env.APP_PATH }}

          VERSION="${{ needs.bump-version.outputs.version }}"
          IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
          ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"

          echo "📝 Applying version bump for iOS build: $VERSION (iOS Build: $IOS_BUILD, Android Build: $ANDROID_BUILD)"

          # Use version-manager script to apply versions
          node ${{ env.APP_PATH }}/scripts/version-manager.cjs apply "$VERSION" "$IOS_BUILD" "$ANDROID_BUILD"

      - name: Set up Xcode
        if: inputs.platform != 'android'
        uses: maxim-lobanov/setup-xcode@v1
        with:
          xcode-version: ${{ env.XCODE_VERSION }}
      - name: Configure Xcode path
        if: inputs.platform != 'android'
        run: |
          echo "🔧 Configuring Xcode path to fix iOS SDK issues..."
          # Fix for macOS 15 runner iOS SDK issues
          # See: https://github.com/actions/runner-images/issues/12758
          sudo xcode-select --switch /Applications/Xcode_${{ env.XCODE_VERSION }}.app
          echo "✅ Xcode path configured"

          # Verify Xcode setup
          echo "Xcode version:"
          xcodebuild -version
          echo "Xcode path:"
          xcode-select -p

      - name: Compute .yarnrc.yml hash
        id: yarnrc-hash
        uses: ./.github/actions/yarnrc-hash

      - name: Cache Yarn artifacts
        id: yarn-cache
        uses: ./.github/actions/cache-yarn
        with:
          path: |
            .yarn/cache
            .yarn/install-state.gz
            .yarn/unplugged
          cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}

      - name: Cache Ruby gems
        id: gems-cache
        uses: ./.github/actions/cache-bundler
        with:
          # TODO(jcortejoso): Confirm the path of the bundle cache
          path: ${{ env.APP_PATH }}/ios/vendor/bundle
          lock-file: app/Gemfile.lock
          cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}

      - name: Cache CocoaPods
        id: pods-cache
        uses: ./.github/actions/cache-pods
        with:
          path: |
            ${{ env.APP_PATH }}/ios/Pods
            ~/Library/Caches/CocoaPods
          lockfile: app/ios/Podfile.lock
          cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_PODS_CACHE_VERSION }}

      - name: Log cache status
        run: |
          echo "Cache hit results:"
          echo "- Yarn cache hit: ${{ steps.yarn-cache.outputs.cache-hit }}"
          echo "- Gems cache hit: ${{ steps.gems-cache.outputs.cache-hit }}"
          echo "- Pods cache hit: ${{ steps.pods-cache.outputs.cache-hit }}"

      - name: Disable Yarn hardened mode
        run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV

      - name: Verify lock files are up to date
        run: |
          echo "🔍 Checking if lock files are in sync with dependency files..."

          # For yarn workspaces, yarn.lock is at root
          if [ ! -f "${{ env.WORKSPACE }}/yarn.lock" ]; then
            echo "❌ ERROR: yarn.lock file is missing at workspace root!"
            echo "Run 'yarn install' at the repository root and commit the yarn.lock file."
            exit 1
          fi

          # Gemfile.lock is in app directory
          if [ ! -f "${{ env.APP_PATH }}/Gemfile.lock" ]; then
            echo "❌ ERROR: Gemfile.lock file is missing!"
            echo "Run 'bundle install' in the app directory and commit the Gemfile.lock file."
            exit 1
          fi

          echo "✅ Lock files exist"
      - name: Check Java installation
        run: |
          echo "INSTALL_JAVA=false" >> "$GITHUB_ENV"
          if command -v java &> /dev/null && java -version &> /dev/null; then
            echo "Java already installed: $(java -version 2>&1 | head -n 1)"
          else
            echo "Java not found or not working, will install..."
            echo "INSTALL_JAVA=true" >> "$GITHUB_ENV"
          fi
      - name: Setup Java environment
        if: env.INSTALL_JAVA == 'true'
        uses: actions/setup-java@v4
        with:
          distribution: "temurin"
          java-version: ${{ env.JAVA_VERSION }}
      - name: Generate token for self repositories
        if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
        uses: ./.github/actions/generate-github-token
        id: github-token
        with:
          app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
          private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
          configure-netrc: "true"

      - name: Install Mobile Dependencies (main repo)
        if: inputs.platform != 'android' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
        uses: ./.github/actions/mobile-setup
        with:
          app_path: ${{ env.APP_PATH }}
          node_version: ${{ env.NODE_VERSION }}
          ruby_version: ${{ env.RUBY_VERSION }}
          workspace: ${{ env.WORKSPACE }}
        env:
          SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}

      - name: Install Mobile Dependencies (forked PRs - no secrets)
        if: inputs.platform != 'android' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
        uses: ./.github/actions/mobile-setup
        with:
          app_path: ${{ env.APP_PATH }}
          node_version: ${{ env.NODE_VERSION }}
          ruby_version: ${{ env.RUBY_VERSION }}
          workspace: ${{ env.WORKSPACE }}

      - name: Verify iOS Secrets
        if: inputs.platform != 'android'
        run: |
          # Verify App Store Connect API Key exists and contains PEM header
          if [ -z "${{ secrets.IOS_CONNECT_API_KEY_BASE64 }}" ]; then
            echo "❌ Error: App Store Connect API Key cannot be empty"
            exit 1
          fi
          # Verify Issuer ID is in correct format (UUID)
          if ! echo "${{ secrets.IOS_CONNECT_ISSUER_ID }}" | grep -E "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" >/dev/null; then
            echo "❌ Error: Invalid App Store Connect Issuer ID format (should be UUID)"
            exit 1
          fi
          # Verify Key ID is in correct format (alphanumeric)
          if ! echo "${{ secrets.IOS_CONNECT_KEY_ID }}" | grep -E "^[A-Z0-9]{10}$" >/dev/null; then
            echo "❌ Error: Invalid App Store Connect Key ID format"
            exit 1
          fi
          # Verify P12 password is not empty and meets basic security requirements
          if [ -z "${{ secrets.IOS_P12_PASSWORD }}" ]; then
            echo "❌ Error: P12 password cannot be empty"
            exit 1
          fi
          # Verify base64 secrets are not empty
          if [ -z "${{ secrets.IOS_DIST_CERT_BASE64 }}" ]; then
            echo "❌ Error: Distribution certificate cannot be empty"
            exit 1
          fi
          if [ -z "${{ secrets.IOS_PROV_PROFILE_BASE64 }}" ]; then
            echo "❌ Error: Provisioning profile cannot be empty"
            exit 1
          fi
          echo "✅ All iOS secrets verified successfully!"

      - name: Decode certificate and profile
        if: inputs.platform != 'android'
        run: |
          mkdir -p "${{ env.APP_PATH }}$(dirname "${{ env.IOS_DIST_CERT_PATH }}")"
          echo "${{ secrets.IOS_DIST_CERT_BASE64 }}" | base64 --decode > ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}
          echo "${{ secrets.IOS_PROV_PROFILE_BASE64 }}" | base64 --decode > ${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}
          echo "${{ secrets.IOS_CONNECT_API_KEY_BASE64 }}" | base64 --decode > ${{ env.APP_PATH }}${{ env.IOS_CONNECT_API_KEY_PATH }}

      # for debugging...which can take some time :(
      - name: Verify ios secret checksums
        if: false # for debugging
        run: |
          echo "SHA256 of dist_cert.p12:"
          shasum -a 256 ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}
          echo "SHA256 of profile.mobileprovision:"
          shasum -a 256 ${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}
          echo "SHA256 of connect_api_key.p8:"
          shasum -a 256 ${{ env.APP_PATH }}${{ env.IOS_CONNECT_API_KEY_PATH }}
          echo "Certificate file size:"
          ls -l ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}
          echo "SHA256 of password:"
          echo -n "${{ secrets.IOS_P12_PASSWORD }}" | shasum -a 256
          echo "SHA256 of connect_api_key_base64:"
          echo -n "${{ secrets.IOS_CONNECT_API_KEY_BASE64 }}" | shasum -a 256
          echo "Verifying certificate..."
          if openssl pkcs12 -in ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }} -password pass:'${{ secrets.IOS_P12_PASSWORD }}' -info >/dev/null 2>&1 || openssl pkcs12 -in ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }} -password pass:'${{ secrets.IOS_P12_PASSWORD }}' -info 2>&1 | grep -q "MAC:"; then
            echo "✅ Certificate verification successful (algorithm warning can be safely ignored)"
          else
            echo "❌ Certificate verification failed - please check certificate validity"
            exit 1
          fi

      - name: Verify iOS certificate and environment
        if: inputs.platform != 'android' && !env.ACT
        run: |
          # Check if certificate directory exists
          if [ ! -d "${{ env.APP_PATH }}/ios/certs" ]; then
            echo "❌ Error: iOS certificates directory not found at ${{ env.APP_PATH }}/ios/certs"
            exit 1
          fi

          # Check if certificate file exists
          if [ ! -f "${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}" ]; then
            echo "❌ Error: Distribution certificate not found at ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}"
            exit 1
          fi

          # Check certificate file permissions
          CERT_PERMS=$(ls -l "${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}" | awk '{print $1}')
          if [ "$CERT_PERMS" != "-rw-r--r--" ]; then
            echo "❌ Error: Distribution certificate has incorrect permissions: $CERT_PERMS"
            echo "Expected: -rw-r--r--"
            exit 1
          fi

          # Check certificate file size
          CERT_SIZE=$(stat -f%z "${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}" 2>/dev/null || stat -c%s "${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}")
          if [ "$CERT_SIZE" -lt 1000 ]; then
            echo "❌ Error: Distribution certificate file size ($CERT_SIZE bytes) is suspiciously small"
            exit 1
          fi

          # Check if we can create a test keychain
          TEST_KEYCHAIN="test.keychain"
          if ! security create-keychain -p "" "$TEST_KEYCHAIN" >/dev/null 2>&1; then
            echo "❌ Error: Unable to create test keychain. Check permissions."
            exit 1
          fi
          security delete-keychain "$TEST_KEYCHAIN" >/dev/null 2>&1

          echo "✅ Certificate and environment verification passed!"

      - name: Install certificate
        if: inputs.platform != 'android' && !env.ACT
        run: |
          security create-keychain -p "" build.keychain >/dev/null 2>&1
          security default-keychain -s build.keychain >/dev/null 2>&1
          security unlock-keychain -p "" build.keychain >/dev/null 2>&1
          security import ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }} -k build.keychain -P '${{ secrets.IOS_P12_PASSWORD }}' -T /usr/bin/codesign >/dev/null 2>&1
          security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain >/dev/null 2>&1

      - name: Install provisioning profile
        if: inputs.platform != 'android' && !env.ACT
        env:
          IOS_APP_IDENTIFIER: ${{ secrets.IOS_APP_IDENTIFIER }}
          IOS_PROV_PROFILE_NAME: ${{ secrets.IOS_PROV_PROFILE_NAME }}
          IOS_PROV_PROFILE_PATH: ${{ env.IOS_PROV_PROFILE_PATH }}
          IOS_TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
        run: |
          # Verify file exists before proceeding
          echo "Checking for provisioning profile at: ${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}"
          ls -l "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}"
          if [ ! -f "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}" ]; then
            echo "❌ Error: Provisioning profile not found at specified path."
            exit 1
          fi
          echo "Provisioning profile found."

          # Print file details
          echo "Provisioning Profile File Details:"
          echo "--------------------------------"
          echo "File size: $(stat -f%z "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}" 2>/dev/null || stat -c%s "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}") bytes"
          echo "File permissions: $(ls -l "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}" | awk '{print $1}')"
          echo "File owner: $(ls -l "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}" | awk '{print $3}')"
          echo "--------------------------------"

          # Create a temporary plist file to extract UUID
          TEMP_PLIST_PATH=$(mktemp /tmp/profile_plist.XXXXXX)

          # Extract plist from mobileprovision file
          echo "Extracting plist from provisioning profile..."
          security cms -D -i "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}" -o "$TEMP_PLIST_PATH"
          if [ $? -ne 0 ]; then
            echo "❌ Error: Failed to extract plist from provisioning profile"
            exit 1
          fi

          # Extract UUID and profile name from plist
          echo "Extracting UUID and profile name from plist..."
          PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print :UUID" "$TEMP_PLIST_PATH" 2>/dev/null)
          if [ $? -ne 0 ] || [ -z "$PROFILE_UUID" ]; then
            echo "❌ Error: Failed to extract UUID from provisioning profile"
            cat "$TEMP_PLIST_PATH" | head -20
            exit 1
          fi

          # Extract the actual profile name from within the file
          PROFILE_NAME=$(/usr/libexec/PlistBuddy -c "Print :Name" "$TEMP_PLIST_PATH" 2>/dev/null)
          if [ $? -ne 0 ] || [ -z "$PROFILE_NAME" ]; then
            echo "⚠️ Warning: Failed to extract Name from provisioning profile, will use provided IOS_PROV_PROFILE_NAME"
            PROFILE_NAME="$IOS_PROV_PROFILE_NAME"
          fi

          echo "Profile UUID: $PROFILE_UUID"
          echo "Profile Name: $PROFILE_NAME"

          # Install provisioning profile in the correct location with UUID filename
          echo "Installing provisioning profile in filesystem..."
          mkdir -p "/Users/runner/Library/MobileDevice/Provisioning Profiles"

          # Copy with the UUID as filename
          UUID_TARGET_PATH="/Users/runner/Library/MobileDevice/Provisioning Profiles/${PROFILE_UUID}.mobileprovision"
          cp -v "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}" "$UUID_TARGET_PATH"

          # Set correct permissions on the profile
          chmod 644 "$UUID_TARGET_PATH"
          chown runner:staff "$UUID_TARGET_PATH"

          # Save the profile path and name to environment for later steps
          echo "IOS_PROV_PROFILE_PATH=$UUID_TARGET_PATH" >> $GITHUB_ENV
          echo "IOS_PROV_PROFILE_NAME=$PROFILE_NAME" >> $GITHUB_ENV

          # Print provisioning profile information
          echo "Provisioning Profile Information:"
          echo "--------------------------------"
          echo "Profile Name (from file): $PROFILE_NAME"
          echo "Profile Name (from env): $IOS_PROV_PROFILE_NAME"
          echo "Profile Path: $UUID_TARGET_PATH"
          echo "Profile UUID: $PROFILE_UUID"
          echo "App Identifier: $IOS_APP_IDENTIFIER"
          echo "Team ID: $IOS_TEAM_ID"
          echo "--------------------------------"

          # List all provisioning profiles in the system with detailed info
          echo "List of all provisioning profiles in system:"
          ls -la "/Users/runner/Library/MobileDevice/Provisioning Profiles/"

          # Clean up temp file
          rm -f "$TEMP_PLIST_PATH"

          echo "✅ Provisioning profile installation steps completed."

      - name: Build Dependencies (iOS)
        if: inputs.platform != 'android'
        run: |
          echo "🏗️ Building SDK dependencies..."
          cd ${{ env.APP_PATH }}
          yarn workspace @selfxyz/mobile-app run build:deps --silent || { echo "❌ Dependency build failed"; exit 1; }
          echo "✅ Dependencies built successfully"

      # act won't work with macos, but you can test with `bundle exec fastlane ios ...`
      - name: Build and upload to App Store Connect/TestFlight
        if: inputs.platform != 'android' && !env.ACT
        env:
          CI_VERSION: ${{ needs.bump-version.outputs.version }}
          CI_IOS_BUILD: ${{ needs.bump-version.outputs.ios_build }}
          CI_ANDROID_BUILD: ${{ needs.bump-version.outputs.android_build }}
          ENABLE_DEBUG_LOGS: ${{ secrets.ENABLE_DEBUG_LOGS }}
          GRAFANA_LOKI_PASSWORD: ${{ secrets.GRAFANA_LOKI_PASSWORD }}
          GRAFANA_LOKI_URL: ${{ secrets.GRAFANA_LOKI_URL }}
          GRAFANA_LOKI_USERNAME: ${{ secrets.GRAFANA_LOKI_USERNAME }}
          IOS_APP_IDENTIFIER: ${{ secrets.IOS_APP_IDENTIFIER }}
          IOS_CONNECT_API_KEY_BASE64: ${{ secrets.IOS_CONNECT_API_KEY_BASE64 }}
          IOS_CONNECT_API_KEY_PATH: ${{ env.APP_PATH }}${{ env.IOS_CONNECT_API_KEY_PATH }}
          IOS_CONNECT_ISSUER_ID: ${{ secrets.IOS_CONNECT_ISSUER_ID }}
          IOS_CONNECT_KEY_ID: ${{ secrets.IOS_CONNECT_KEY_ID }}
          IOS_P12_PASSWORD: ${{ secrets.IOS_P12_PASSWORD }}
          IOS_PROJECT_NAME: ${{ secrets.IOS_PROJECT_NAME }}
          IOS_PROJECT_SCHEME: ${{ secrets.IOS_PROJECT_SCHEME }}
          IOS_PROV_PROFILE_DIR: ${{ env.IOS_PROV_PROFILE_DIRECTORY }}
          IOS_PROV_PROFILE_NAME: ${{ secrets.IOS_PROV_PROFILE_NAME }}
          IOS_PROV_PROFILE_PATH: ${{ env.IOS_PROV_PROFILE_PATH }}
          IOS_SIGNING_CERTIFICATE: ${{ secrets.IOS_SIGNING_CERTIFICATE }}
          IOS_TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
          IOS_TEAM_NAME: ${{ secrets.IOS_TEAM_NAME }}
          IOS_TESTFLIGHT_GROUPS: ${{ secrets.IOS_TESTFLIGHT_GROUPS }}
          NODE_OPTIONS: "--max-old-space-size=8192"
          SEGMENT_KEY: ${{ secrets.SEGMENT_KEY }}
          SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
          SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
          TURNKEY_AUTH_PROXY_CONFIG_ID: ${{ secrets.TURNKEY_AUTH_PROXY_CONFIG_ID }}
          TURNKEY_GOOGLE_CLIENT_ID: ${{ secrets.TURNKEY_GOOGLE_CLIENT_ID }}
          TURNKEY_ORGANIZATION_ID: ${{ secrets.TURNKEY_ORGANIZATION_ID }}
        timeout-minutes: 90
        run: |
          cd ${{ env.APP_PATH }}
          echo "--- Pre-Fastlane Diagnostics ---"
          echo "Running as user: $(whoami)"
          echo "Default keychain:"
          security list-keychains -d user
          echo "Identities in build.keychain:"
          security find-identity -v -p codesigning build.keychain || echo "Failed to find identities in build.keychain"
          echo "--- Starting Fastlane ---"

          # Determine deployment track and version bump
          DEPLOYMENT_TRACK="${{ inputs.deployment_track || 'internal' }}"
          VERSION_BUMP="${{ needs.bump-version.outputs.version_bump_type }}"
          TEST_MODE="${{ inputs.test_mode || false }}"

          echo "📱 Deployment Configuration:"
          echo "  - Track: $DEPLOYMENT_TRACK"
          echo "  - Version Bump: $VERSION_BUMP (already applied in bump-version job)"
          echo "  - Version: ${{ needs.bump-version.outputs.version }}"
          echo "  - iOS Build: ${{ needs.bump-version.outputs.ios_build }}"
          echo "  - Test Mode: $TEST_MODE"

          if [ "$TEST_MODE" = "true" ]; then
            echo "🧪 Running in TEST MODE - will skip upload to TestFlight"
            bundle exec fastlane ios deploy_auto \
              deployment_track:$DEPLOYMENT_TRACK \
              version_bump:skip \
              test_mode:true \
              --verbose
          else
            echo "🚀 Deploying to App Store Connect..."
            bundle exec fastlane ios deploy_auto \
              deployment_track:$DEPLOYMENT_TRACK \
              version_bump:skip \
              --verbose
          fi

      - name: Verify iOS build output
        if: inputs.platform != 'android'
        run: |
          cd ${{ env.APP_PATH }}

          echo "🔍 Verifying iOS build artifacts..."

          # Find the IPA file
          IPA_PATH=$(find ios/build -name "*.ipa" 2>/dev/null | head -1)

          if [ -z "$IPA_PATH" ]; then
            echo "❌ ERROR: No IPA file found in ios/build directory"
            echo "Build may have failed silently. Check Fastlane logs above."
            exit 1
          fi

          echo "✅ Found IPA: $IPA_PATH"

          # Check file size (should be at least 10MB for a real app)
          IPA_SIZE=$(stat -f%z "$IPA_PATH" 2>/dev/null || stat -c%s "$IPA_PATH")
          IPA_SIZE_MB=$((IPA_SIZE / 1024 / 1024))

          echo "📦 IPA size: ${IPA_SIZE_MB}MB"

          if [ "$IPA_SIZE" -lt 10485760 ]; then
            echo "⚠️  WARNING: IPA file is suspiciously small (< 10MB)"
            echo "This may indicate a build problem."
          fi

          echo "✅ iOS build output verification passed"

      # Version updates moved to separate job to avoid race conditions

      - name: Remove project.pbxproj updates we don't want to commit
        if: inputs.platform != 'android'
        run: |
          PBXPROJ_FILE="app/ios/Self.xcodeproj/project.pbxproj"

          # Create a temporary file to store version info
          echo "Extracting version information..."
          rm -f versions.txt
          grep -E 'CURRENT_PROJECT_VERSION = [0-9]+;|MARKETING_VERSION = [0-9]+\.[0-9]+\.[0-9]+;' "${PBXPROJ_FILE}" > versions.txt

          # Check if we have version information
          if [ -s versions.txt ]; then
            echo "Found version information. Resetting file and re-applying versions..."

            # Store the version values
            CURRENT_VERSION=$(grep 'CURRENT_PROJECT_VERSION' versions.txt | head -1 | sed 's/.*CURRENT_PROJECT_VERSION = \([0-9]*\);.*/\1/')
            MARKETING_VERSION=$(grep 'MARKETING_VERSION' versions.txt | head -1 | sed 's/.*MARKETING_VERSION = \([0-9]*\.[0-9]*\.[0-9]*\);.*/\1/')

            echo "Current version: $CURRENT_VERSION"
            echo "Marketing version: $MARKETING_VERSION"

            # Reset the file to HEAD
            git checkout HEAD -- "${PBXPROJ_FILE}"

            # Update the versions if they exist
            if [ ! -z "$CURRENT_VERSION" ]; then
              sed -i '' "s/\(CURRENT_PROJECT_VERSION = \)[0-9]*;/\1$CURRENT_VERSION;/g" "${PBXPROJ_FILE}"
            fi

            if [ ! -z "$MARKETING_VERSION" ]; then
              sed -i '' "s/\(MARKETING_VERSION = \)[0-9]*\.[0-9]*\.[0-9]*;/\1$MARKETING_VERSION;/g" "${PBXPROJ_FILE}"
            fi

            echo "Version information successfully applied."
          else
            echo "No version information found. Resetting file..."
            git checkout HEAD -- "${PBXPROJ_FILE}"
          fi

          # Clean up
          rm -f versions.txt

      - name: Monitor cache usage
        if: always()
        run: |
          echo "📊 Cache Size Report (iOS Build)"
          echo "================================"

          if [ -d "${{ env.APP_PATH }}/node_modules" ]; then
            NODE_SIZE=$(du -sh "${{ env.APP_PATH }}/node_modules" | cut -f1)
            echo "Node modules: $NODE_SIZE"
          fi

          if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
            GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
            echo "Ruby gems: $GEMS_SIZE"
          fi

          if [ -d "${{ env.APP_PATH }}/ios/Pods" ]; then
            PODS_SIZE=$(du -sh "${{ env.APP_PATH }}/ios/Pods" | cut -f1)
            echo "CocoaPods: $PODS_SIZE"
          fi

          echo "================================"
          echo "💡 GitHub Actions cache limit: 10GB per repository"

  build-android:
    needs: [bump-version]
    runs-on: ubuntu-latest
    permissions:
      contents: read
      actions: write
      id-token: write
    if: |
      (github.event_name != 'pull_request' ||
        (github.event.action == 'closed' && github.event.pull_request.merged == true)) &&
      (
        (inputs.platform == 'android' || inputs.platform == 'both') ||
        (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'deploy:ios'))
      )
    steps:
      - name: Mobile deployment status
        run: |
          echo "🚀 Mobile deployment is enabled - proceeding with Android build"
          echo "📱 Platform: ${{ inputs.platform || 'both' }}"
          echo "🎯 Track: ${{ inputs.deployment_track || 'internal' }}"
          echo "📦 Version bump: ${{ inputs.version_bump || 'build' }}"
          if [ "${{ github.event_name }}" = "pull_request" ]; then
            echo "🔀 Triggered by PR merge: #${{ github.event.pull_request.number }} - ${{ github.event.pull_request.title }}"
            echo "👤 Merged by: ${{ github.event.pull_request.merged_by.login }}"
            if ${{ contains(github.event.pull_request.labels.*.name, 'deploy:android') }}; then
              echo "🏷️  Label: deploy:android (skipping iOS)"
            fi
          fi

      - uses: actions/checkout@v6
        if: inputs.platform != 'ios'
        with:
          fetch-depth: 0
          # Checkout the branch that triggered the workflow
          ref: ${{ github.ref_name }}
      - uses: "google-github-actions/auth@v2"
        with:
          project_id: "plucky-tempo-454713-r0"
          workload_identity_provider: "projects/852920390127/locations/global/workloadIdentityPools/gh-self/providers/github-by-repos"
          service_account: "self-xyz@plucky-tempo-454713-r0.iam.gserviceaccount.com"
      - name: Free up disk space
        uses: ./.github/actions/free-disk-space
      # Fail fast: set up JDK for keytool and verify Android secrets early
      - name: Setup Java environment
        if: inputs.platform != 'ios'
        uses: actions/setup-java@v4
        with:
          distribution: "temurin"
          java-version: ${{ env.JAVA_VERSION }}

      - name: Decode Android Secrets
        if: inputs.platform != 'ios'
        run: |
          echo "${{ secrets.ANDROID_KEYSTORE }}" | base64 --decode > ${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}

      - name: Verify Android Secrets
        if: inputs.platform != 'ios'
        run: |
          # Verify Google Cloud auth via Workload Identity Federation (ADC)
          if [ -z "$GOOGLE_APPLICATION_CREDENTIALS" ] || [ ! -f "$GOOGLE_APPLICATION_CREDENTIALS" ]; then
            echo "❌ Error: GOOGLE_APPLICATION_CREDENTIALS not set or file missing. Ensure google-github-actions/auth ran."
            exit 1
          fi
          # Verify keystore file exists and is valid
          if [ ! -f "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" ]; then
            echo "❌ Error: Keystore file was not created successfully"
            exit 1
          fi
          # Try to verify the keystore with the provided password
          if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >/dev/null 2>&1; then
            echo "❌ Error: Invalid keystore password"
            exit 1
          fi
          # Verify the key alias exists
          if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" -alias "${{ secrets.ANDROID_KEY_ALIAS }}" >/dev/null 2>&1; then
            echo "❌ Error: Key alias '${{ secrets.ANDROID_KEY_ALIAS }}' not found in keystore"
            exit 1
          fi
          # Verify the key password
          if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" -alias "${{ secrets.ANDROID_KEY_ALIAS }}" -keypass "${{ secrets.ANDROID_KEY_PASSWORD }}" >/dev/null 2>&1; then
            echo "❌ Error: Invalid key password"
            exit 1
          fi

          # Detect keystore type and export for later steps
          KEYSTORE_TYPE=$(keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" 2>/dev/null | awk -F': ' '/Keystore type:/ {print $2; exit}')
          if [ -z "$KEYSTORE_TYPE" ]; then
            echo "❌ Error: Unable to determine keystore type"
            exit 1
          fi
          echo "ANDROID_KEYSTORE_TYPE=$KEYSTORE_TYPE" >> "$GITHUB_ENV"
          echo "Detected keystore type: $KEYSTORE_TYPE"

          # Ensure the alias holds a PrivateKeyEntry (required for signing)
          if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" -alias "${{ secrets.ANDROID_KEY_ALIAS }}" -keypass "${{ secrets.ANDROID_KEY_PASSWORD }}" | grep -q "Entry type: PrivateKeyEntry"; then
            echo "❌ Error: Alias '${{ secrets.ANDROID_KEY_ALIAS }}' is not a PrivateKeyEntry"
            exit 1
          fi
          echo "✅ All Android secrets verified successfully!"
      - name: Read and sanitize Node.js version
        shell: bash
        run: |
          if [ ! -f .nvmrc ] || [ -z "$(cat .nvmrc)" ]; then
            echo "❌ .nvmrc is missing or empty"; exit 1;
          fi
          VERSION="$(tr -d '\r\n' < .nvmrc)"
          VERSION="${VERSION#v}"
          if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then
            echo "Invalid .nvmrc content: '$VERSION'"; exit 1;
          fi
          echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV"
          echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV"

      - name: Verify branch and commit (Android)
        if: needs.bump-version.outputs.platform != 'ios'
        run: |
          echo "🔍 Verifying we're building from the correct branch and commit..."
          echo "Current branch: $(git branch --show-current || git symbolic-ref --short HEAD 2>/dev/null || echo 'detached')"
          echo "Current commit: $(git rev-parse HEAD)"
          echo "Current commit message: $(git log -1 --pretty=format:'%s')"
          BUILD_BRANCH="${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || github.ref_name }}"
          echo "Building from branch: $BUILD_BRANCH"
          echo "Target HEAD commit: $(git rev-parse origin/$BUILD_BRANCH)"
          echo "Target HEAD message: $(git log -1 --pretty=format:'%s' origin/$BUILD_BRANCH)"

          if [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/$BUILD_BRANCH)" ]; then
            echo "⚠️ WARNING: Current commit differs from latest $BUILD_BRANCH commit"
            echo "This might indicate we're not building from the latest $BUILD_BRANCH branch"
            git log --oneline HEAD..origin/$BUILD_BRANCH || true
          else
            echo "✅ Building from latest $BUILD_BRANCH commit"
          fi

      - name: Apply version bump for build
        if: needs.bump-version.outputs.platform != 'ios'
        run: |
          cd ${{ env.APP_PATH }}

          VERSION="${{ needs.bump-version.outputs.version }}"
          IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
          ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"

          echo "📝 Applying version bump for Android build: $VERSION (iOS Build: $IOS_BUILD, Android Build: $ANDROID_BUILD)"

          # Use version-manager script to apply versions
          node ${{ env.APP_PATH }}/scripts/version-manager.cjs apply "$VERSION" "$IOS_BUILD" "$ANDROID_BUILD"

      - name: Compute .yarnrc.yml hash
        id: yarnrc-hash
        uses: ./.github/actions/yarnrc-hash

      - name: Cache Yarn artifacts
        id: yarn-cache
        uses: ./.github/actions/cache-yarn
        with:
          path: |
            .yarn/cache
            .yarn/install-state.gz
            .yarn/unplugged
          cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}

      - name: Cache Ruby gems
        id: gems-cache
        uses: ./.github/actions/cache-bundler
        with:
          path: ${{ env.APP_PATH }}/vendor/bundle
          lock-file: app/Gemfile.lock
          cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}

      - name: Cache Gradle dependencies
        id: gradle-cache
        uses: ./.github/actions/cache-gradle
        with:
          cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GRADLE_CACHE_VERSION }}

      - name: Cache Android NDK
        id: ndk-cache
        uses: actions/cache@v4
        with:
          path: ${{ env.ANDROID_SDK_ROOT }}/ndk/${{ env.ANDROID_NDK_VERSION }}
          key: ${{ runner.os }}-ndk-${{ env.ANDROID_NDK_VERSION }}

      - name: Log cache status
        run: |
          echo "Cache hit results:"
          echo "- Yarn cache hit: ${{ steps.yarn-cache.outputs.cache-hit }}"
          echo "- Gems cache hit: ${{ steps.gems-cache.outputs.cache-hit }}"
          echo "- Gradle cache hit: ${{ steps.gradle-cache.outputs.cache-hit }}"
          echo "- NDK cache hit: ${{ steps.ndk-cache.outputs.cache-hit }}"

      - name: Disable Yarn hardened mode
        run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV

      - name: Verify lock files are up to date
        run: |
          echo "🔍 Checking if lock files are in sync with dependency files..."

          # For yarn workspaces, yarn.lock is at root
          if [ ! -f "${{ env.WORKSPACE }}/yarn.lock" ]; then
            echo "❌ ERROR: yarn.lock file is missing at workspace root!"
            echo "Run 'yarn install' at the repository root and commit the yarn.lock file."
            exit 1
          fi

          # Gemfile.lock is in app directory
          if [ ! -f "${{ env.APP_PATH }}/Gemfile.lock" ]; then
            echo "❌ ERROR: Gemfile.lock file is missing!"
            echo "Run 'bundle install' in the app directory and commit the Gemfile.lock file."
            exit 1
          fi

          echo "✅ Lock files exist"

      - name: Generate token for self repositories
        if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
        uses: ./.github/actions/generate-github-token
        id: github-token
        with:
          app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
          private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}

      - name: Install Mobile Dependencies (main repo)
        if: inputs.platform != 'ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
        uses: ./.github/actions/mobile-setup
        with:
          app_path: ${{ env.APP_PATH }}
          node_version: ${{ env.NODE_VERSION }}
          ruby_version: ${{ env.RUBY_VERSION }}
          workspace: ${{ env.WORKSPACE }}
        env:
          SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
          PLATFORM: ${{ inputs.platform }}

      - name: Install Mobile Dependencies (forked PRs - no secrets)
        if: inputs.platform != 'ios' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
        uses: ./.github/actions/mobile-setup
        with:
          app_path: ${{ env.APP_PATH }}
          node_version: ${{ env.NODE_VERSION }}
          ruby_version: ${{ env.RUBY_VERSION }}
          workspace: ${{ env.WORKSPACE }}
        env:
          PLATFORM: ${{ inputs.platform }}

      # android specific steps

      - name: Setup Android SDK
        if: inputs.platform != 'ios'
        uses: android-actions/setup-android@v3
        with:
          accept-android-sdk-licenses: true

      - name: Install NDK
        if: inputs.platform != 'ios' && steps.ndk-cache.outputs.cache-hit != 'true'
        uses: nick-fields/retry@v3
        with:
          timeout_minutes: 15
          max_attempts: 5
          retry_wait_seconds: 10
          command: sdkmanager "ndk;${{ env.ANDROID_NDK_VERSION }}"

      - name: Install CMake
        if: inputs.platform != 'ios' && steps.ndk-cache.outputs.cache-hit != 'true'
        uses: nick-fields/retry@v3
        with:
          timeout_minutes: 10
          max_attempts: 5
          retry_wait_seconds: 10
          command: sdkmanager "cmake;3.22.1"

      - name: Set Gradle JVM options
        if: inputs.platform != 'ios' # Apply to CI builds (not just ACT)
        run: |
          echo "org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -Dfile.encoding=UTF-8" >> ${{ env.APP_PATH }}/android/gradle.properties

      - name: Install Python dependencies for Play Store upload
        if: inputs.platform != 'ios'
        run: |
          python -m pip install --upgrade pip
          pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client

      - name: Setup Android private modules
        if: inputs.platform != 'ios'
        run: |
          cd ${{ env.APP_PATH }}
          PLATFORM=android node scripts/setup-private-modules.cjs
        env:
          SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
          CI: true

      - name: Build Dependencies (Android)
        if: inputs.platform != 'ios'
        run: |
          echo "🏗️ Building SDK dependencies..."
          cd ${{ env.APP_PATH }}
          yarn workspace @selfxyz/mobile-app run build:deps --silent || { echo "❌ Dependency build failed"; exit 1; }
          echo "✅ Dependencies built successfully"

      - name: Build AAB with Fastlane
        if: inputs.platform != 'ios'
        env:
          CI_VERSION: ${{ needs.bump-version.outputs.version }}
          CI_IOS_BUILD: ${{ needs.bump-version.outputs.ios_build }}
          CI_ANDROID_BUILD: ${{ needs.bump-version.outputs.android_build }}
          ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
          ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
          ANDROID_KEYSTORE: ${{ secrets.ANDROID_KEYSTORE }}
          ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
          ANDROID_KEYSTORE_PATH: ${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}
          ANDROID_PACKAGE_NAME: ${{ secrets.ANDROID_PACKAGE_NAME }}
          ENABLE_DEBUG_LOGS: ${{ secrets.ENABLE_DEBUG_LOGS }}
          GOOGLE_SIGNIN_ANDROID_CLIENT_ID: ${{ secrets.GOOGLE_SIGNIN_ANDROID_CLIENT_ID }}
          GRAFANA_LOKI_PASSWORD: ${{ secrets.GRAFANA_LOKI_PASSWORD }}
          GRAFANA_LOKI_URL: ${{ secrets.GRAFANA_LOKI_URL }}
          GRAFANA_LOKI_USERNAME: ${{ secrets.GRAFANA_LOKI_USERNAME }}
          NODE_OPTIONS: "--max-old-space-size=6144"
          SEGMENT_KEY: ${{ secrets.SEGMENT_KEY }}
          SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
          TURNKEY_AUTH_PROXY_CONFIG_ID: ${{ secrets.TURNKEY_AUTH_PROXY_CONFIG_ID }}
          TURNKEY_GOOGLE_CLIENT_ID: ${{ secrets.TURNKEY_GOOGLE_CLIENT_ID }}
          TURNKEY_ORGANIZATION_ID: ${{ secrets.TURNKEY_ORGANIZATION_ID }}
        run: |
          cd ${{ env.APP_PATH }}

          # Determine deployment track and version bump
          DEPLOYMENT_TRACK="${{ inputs.deployment_track || 'internal' }}"
          VERSION_BUMP="${{ needs.bump-version.outputs.version_bump_type }}"
          TEST_MODE="${{ inputs.test_mode || false }}"

          echo "🤖 Build Configuration:"
          echo "  - Track: $DEPLOYMENT_TRACK"
          echo "  - Version Bump: $VERSION_BUMP (already applied in bump-version job)"
          echo "  - Version: ${{ needs.bump-version.outputs.version }}"
          echo "  - Android Build: ${{ needs.bump-version.outputs.android_build }}"
          echo "  - Test Mode: $TEST_MODE"

          echo "🔨 Building AAB with Fastlane..."
          bundle exec fastlane android build_only \
            deployment_track:$DEPLOYMENT_TRACK \
            version_bump:skip \
            --verbose

      - name: Verify Android build output
        if: inputs.platform != 'ios'
        run: |
          cd ${{ env.APP_PATH }}

          echo "🔍 Verifying Android build artifacts..."

          # Check for AAB file
          AAB_PATH="android/app/build/outputs/bundle/release/app-release.aab"

          if [ ! -f "$AAB_PATH" ]; then
            echo "❌ ERROR: AAB file not found at $AAB_PATH"
            echo "Build may have failed silently. Check Fastlane logs above."
            exit 1
          fi

          echo "✅ Found AAB: $AAB_PATH"

          # Check file size (should be at least 5MB for a real app)
          AAB_SIZE=$(stat -c%s "$AAB_PATH" 2>/dev/null || stat -f%z "$AAB_PATH")
          AAB_SIZE_MB=$((AAB_SIZE / 1024 / 1024))

          echo "📦 AAB size: ${AAB_SIZE_MB}MB"

          if [ "$AAB_SIZE" -lt 5242880 ]; then
            echo "⚠️  WARNING: AAB file is suspiciously small (< 5MB)"
            echo "This may indicate a build problem."
          fi

          echo "✅ Android build output verification passed"

      - name: Clean up Gradle build artifacts
        if: inputs.platform != 'ios'
        uses: ./.github/actions/cleanup-gradle-artifacts

      - name: Upload to Google Play Store using WIF
        if: inputs.platform != 'ios' && inputs.test_mode != true
        timeout-minutes: 10
        run: |
          cd ${{ env.APP_PATH }}

          # Determine deployment track
          DEPLOYMENT_TRACK="${{ inputs.deployment_track || 'internal' }}"

          echo "🚀 Uploading to Google Play Store using Workload Identity Federation..."
          python scripts/upload_to_play_store.py \
            --aab "android/app/build/outputs/bundle/release/app-release.aab" \
            --package-name "${{ secrets.ANDROID_PACKAGE_NAME }}" \
            --track "$DEPLOYMENT_TRACK"

      - name: Monitor cache usage
        if: always()
        run: |
          echo "📊 Cache Size Report (Android Build)"
          echo "===================================="

          if [ -d "${{ env.APP_PATH }}/node_modules" ]; then
            NODE_SIZE=$(du -sh "${{ env.APP_PATH }}/node_modules" | cut -f1)
            echo "Node modules: $NODE_SIZE"
          fi

          if [ -d "${{ env.APP_PATH }}/ios/vendor/bundle" ]; then
            GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/ios/vendor/bundle" | cut -f1)
            echo "Ruby gems: $GEMS_SIZE"
          fi

          if [ -d "$HOME/.gradle/caches" ]; then
            GRADLE_SIZE=$(du -sh "$HOME/.gradle/caches" | cut -f1)
            echo "Gradle caches: $GRADLE_SIZE"
          fi

          if [ -d "${{ env.ANDROID_SDK_ROOT }}/ndk/${{ env.ANDROID_NDK_VERSION }}" ]; then
            NDK_SIZE=$(du -sh "${{ env.ANDROID_SDK_ROOT }}/ndk/${{ env.ANDROID_NDK_VERSION }}" | cut -f1)
            echo "Android NDK: $NDK_SIZE"
          fi

          echo "===================================="
          echo "💡 GitHub Actions cache limit: 10GB per repository"

  # Consolidated version bump PR - runs after both platforms complete
  # NOTE: This job checks out the TARGET branch for version bump (default: dev)
  # This is DIFFERENT from the build branch - we build from staging/feature branch,
  # but create the version bump PR to dev so it can be reviewed before merging to staging
  create-version-bump-pr:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    needs: [bump-version, build-ios, build-android]
    if: |
      always() &&
      (github.event_name != 'pull_request' ||
        (github.event.action == 'closed' && github.event.pull_request.merged == true)) &&
      (needs.build-ios.result == 'success' || needs.build-android.result == 'success')
    env:
      APP_PATH: ${{ github.workspace }}/app
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
          # Checkout target branch for version bump PR (always dev)
          ref: "dev"

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: .nvmrc

      - name: Apply version bump from outputs
        run: |
          cd ${{ env.APP_PATH }}

          VERSION="${{ needs.bump-version.outputs.version }}"
          IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
          ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"
          IOS_SUCCESS="${{ needs.build-ios.result }}"
          ANDROID_SUCCESS="${{ needs.build-android.result }}"

          echo "📝 Applying version bump: $VERSION (iOS: $IOS_BUILD, Android: $ANDROID_BUILD)"

          # Update package.json version
          node -e "
            const fs = require('fs');
            const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
            pkg.version = '$VERSION';
            fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
            console.log('✅ Updated package.json');
          "

          # Update version.json build numbers and deployment timestamps
          node -e "
            const fs = require('fs');
            const version = JSON.parse(fs.readFileSync('version.json', 'utf8'));
            const timestamp = new Date().toISOString();

            // Only bump build numbers for successful builds
            if ('$IOS_SUCCESS' === 'success') {
              version.ios.build = $IOS_BUILD;
              version.ios.lastDeployed = timestamp;
              console.log('✅ Updated iOS build number to $IOS_BUILD and lastDeployed timestamp');
            } else {
              console.log('⏭️  Skipped iOS build number bump (build did not succeed)');
            }

            if ('$ANDROID_SUCCESS' === 'success') {
              version.android.build = $ANDROID_BUILD;
              version.android.lastDeployed = timestamp;
              console.log('✅ Updated Android build number to $ANDROID_BUILD and lastDeployed timestamp');
            } else {
              console.log('⏭️  Skipped Android build number bump (build did not succeed)');
            }

            fs.writeFileSync('version.json', JSON.stringify(version, null, 2) + '\n');
            console.log('✅ Updated version.json');
          "

          echo "✅ Versions applied successfully"

      - name: Verify version changes
        run: |
          cd ${{ env.APP_PATH }}

          # Check that version files actually changed
          if ! git diff --quiet package.json version.json; then
            echo "✅ Version changes detected"
            git diff package.json version.json
          else
            echo "⚠️  No version changes detected in package.json or version.json"
            echo "This may indicate a problem with version application"
            exit 1
          fi

      - name: Determine platforms that succeeded and PR title
        id: platforms
        run: |
          VERSION="${{ needs.bump-version.outputs.version }}"
          PLATFORM="${{ needs.bump-version.outputs.platform }}"
          IOS_RESULT="${{ needs.build-ios.result }}"
          ANDROID_RESULT="${{ needs.build-android.result }}"

          # Determine what was actually built
          PLATFORMS_BUILT=""
          if [ "$IOS_RESULT" = "success" ]; then
            PLATFORMS_BUILT="iOS"
          fi
          if [ "$ANDROID_RESULT" = "success" ]; then
            if [ -n "$PLATFORMS_BUILT" ]; then
              PLATFORMS_BUILT="${PLATFORMS_BUILT} & Android"
            else
              PLATFORMS_BUILT="Android"
            fi
          fi

          # Generate PR title based on what was bumped
          if [ "$PLATFORM" = "ios" ]; then
            PR_TITLE="chore: bump iOS version to $VERSION"
          elif [ "$PLATFORM" = "android" ]; then
            PR_TITLE="chore: bump Android version to $VERSION"
          else
            PR_TITLE="chore: bump mobile app version to $VERSION"
          fi

          echo "platforms=${PLATFORMS_BUILT}" >> $GITHUB_OUTPUT
          echo "pr_title=${PR_TITLE}" >> $GITHUB_OUTPUT
          echo "📱 Successful builds: $PLATFORMS_BUILT"
          echo "📝 PR title: $PR_TITLE"

      - name: Create version bump PR
        if: inputs.dry_run != true
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          VERSION="${{ needs.bump-version.outputs.version }}"
          TARGET_BRANCH="dev"
          # Add timestamp to branch name to avoid collisions
          TIMESTAMP=$(date +%s%N | cut -b1-13)  # Milliseconds since epoch (13 digits)
          BRANCH_NAME="ci/bump-mobile-version-${VERSION}-${TIMESTAMP}"
          PR_TITLE="${{ steps.platforms.outputs.pr_title }}"

          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

          # Check if a PR already exists for this version (avoid duplicate PRs)
          EXISTING_PR=$(gh pr list --base "${TARGET_BRANCH}" --state open --json number,title,headRefName --jq ".[] | select(.title | contains(\"${VERSION}\")) | .number" | head -1)

          if [ -n "$EXISTING_PR" ]; then
            echo "⚠️  PR #${EXISTING_PR} already exists for version ${VERSION}"
            echo "ℹ️  Skipping PR creation to avoid duplicates"
            echo "ℹ️  Existing PR: https://github.com/${{ github.repository }}/pull/${EXISTING_PR}"
            exit 0
          fi

          # Commit the version changes
          cd ${{ env.APP_PATH }}
          git add package.json version.json

          if git diff --cached --quiet; then
            echo "⚠️  No version changes to commit"
            exit 0
          fi

          git commit -m "chore: bump mobile app version to $VERSION" -m "Update build numbers and deployment timestamps after successful deployment."

          # Create new branch from current HEAD (bump target branch with version bump)
          git checkout -b ${BRANCH_NAME}

          # Push the branch
          git push --set-upstream origin ${BRANCH_NAME}

          # Create PR to target branch (usually dev)
          echo "Creating PR to ${TARGET_BRANCH}..."
          gh pr create \
            --base ${TARGET_BRANCH} \
            --head ${BRANCH_NAME} \
            --title "${PR_TITLE}" \
            --body "🤖 Automated version bump after successful deployment

          **Version:** $VERSION
          **iOS Build:** ${{ needs.bump-version.outputs.ios_build }}
          **Android Build:** ${{ needs.bump-version.outputs.android_build }}
          **Platforms Built:** ${{ steps.platforms.outputs.platforms }}
          **Build Branch:** ${{ github.ref_name }}
          **Target Branch:** ${TARGET_BRANCH}

          This PR updates:
          - Build numbers for deployed platforms
          - Deployment timestamps (\`lastDeployed\`) for successful builds

          This PR was automatically created by the mobile deployment workflow." \
            --label "automated"

          echo "✅ Version bump PR created successfully to ${TARGET_BRANCH}"

  # Create git tags after successful deployment
  create-release-tags:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    needs: [bump-version, build-ios, build-android, create-version-bump-pr]
    if: |
      always() &&
      (inputs.dry_run != true) &&
      needs.create-version-bump-pr.result == 'success' &&
      (needs.build-ios.result == 'success' || needs.build-android.result == 'success') &&
      (inputs.deployment_track == 'production')
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
          # Checkout target branch for tagging (always dev)
          ref: "dev"
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Configure Git
        run: |
          git config --global user.name "GitHub Actions"
          git config --global user.email "actions@github.com"

      - name: Create and push tags
        run: |
          cd ${{ env.APP_PATH }}

          # Use version info from bump-version outputs
          VERSION="${{ needs.bump-version.outputs.version }}"
          IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
          ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"

          echo "📦 Creating tags for version $VERSION"

          # Create main version tag (idempotent)
          if git tag -a "v${VERSION}" -m "Release ${VERSION}" 2>/dev/null; then
            echo "✅ Created tag: v${VERSION}"
          else
            EXIT_CODE=$?
            if [ $EXIT_CODE -eq 128 ]; then
              echo "⏭️  Tag v${VERSION} already exists"
            else
              echo "❌ Failed to create tag v${VERSION} with exit code $EXIT_CODE"
              exit 1
            fi
          fi

          # Create platform-specific tags if deployments succeeded (idempotent)
          if [ "${{ needs.build-ios.result }}" = "success" ]; then
            TAG_NAME="v${VERSION}-ios-${IOS_BUILD}"
            if git tag -a "${TAG_NAME}" -m "iOS Release ${VERSION} (Build ${IOS_BUILD})" 2>/dev/null; then
              echo "✅ Created tag: ${TAG_NAME}"
            else
              EXIT_CODE=$?
              if [ $EXIT_CODE -eq 128 ]; then
                echo "⏭️  Tag ${TAG_NAME} already exists"
              else
                echo "❌ Failed to create tag ${TAG_NAME} with exit code $EXIT_CODE"
                exit 1
              fi
            fi
          fi

          if [ "${{ needs.build-android.result }}" = "success" ]; then
            TAG_NAME="v${VERSION}-android-${ANDROID_BUILD}"
            if git tag -a "${TAG_NAME}" -m "Android Release ${VERSION} (Build ${ANDROID_BUILD})" 2>/dev/null; then
              echo "✅ Created tag: ${TAG_NAME}"
            else
              EXIT_CODE=$?
              if [ $EXIT_CODE -eq 128 ]; then
                echo "⏭️  Tag ${TAG_NAME} already exists"
              else
                echo "❌ Failed to create tag ${TAG_NAME} with exit code $EXIT_CODE"
                exit 1
              fi
            fi
          fi

          # Push all tags (force to handle any conflicts)
          if git push origin --tags 2>/dev/null; then
            echo "🚀 Tags pushed to repository"
          else
            echo "⚠️  Some tags may already exist on remote, trying force push..."
            git push origin --tags --force
            echo "🚀 Tags force-pushed to repository"
          fi

      - name: Generate changelog for release
        id: changelog
        run: |
          cd ${{ env.APP_PATH }}

          VERSION="${{ needs.bump-version.outputs.version }}"
          IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
          ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"

          # Find the previous version tag
          PREV_TAG=$(git tag -l "v*" | grep -v "-" | sort -V | tail -2 | head -1 || echo "")

          # Generate simple changelog
          echo "## What's Changed" > release_notes.md
          echo "" >> release_notes.md

          if [ -n "$PREV_TAG" ]; then
            git log --pretty=format:"- %s" ${PREV_TAG}..HEAD --no-merges | grep -v "^- Merge" >> release_notes.md
          else
            echo "Initial release" >> release_notes.md
          fi

          echo "" >> release_notes.md
          echo "## Build Information" >> release_notes.md
          echo "- iOS Build: ${IOS_BUILD}" >> release_notes.md
          echo "- Android Build: ${ANDROID_BUILD}" >> release_notes.md

          # Set output for next step
          echo "version=${VERSION}" >> $GITHUB_OUTPUT

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          tag_name: v${{ steps.changelog.outputs.version }}
          name: Release ${{ steps.changelog.outputs.version }}
          body_path: ${{ env.APP_PATH }}/release_notes.md
          draft: false
          prerelease: false
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
