name: CI

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint-and-type-check:
    name: Code Quality (Strict)
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm install --legacy-peer-deps  # Need --legacy-peer-deps for react-native-windows@0.74.47

      - name: Check formatting (no prettier diff allowed)
        run: npm run format:check

      - name: Run TypeScript type check (strict)
        run: npm run type-check

      - name: Run linter (strict - no warnings allowed)
        run: npm run lint:ci

      - name: Build project
        run: npm run build

      - name: Run unit tests
        run: npm test

  build-android:
    name: Build Android APK
    runs-on: ubuntu-latest
    needs: lint-and-type-check

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

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

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

      - name: Install dependencies
        run: npm install --legacy-peer-deps  # Need --legacy-peer-deps for react-native-windows@0.74.47

      - name: Build library
        run: npm run build

      - name: Install example dependencies
        working-directory: example
        run: npm ci --legacy-peer-deps

      - name: Run library Java unit tests
        working-directory: example/android
        run: |
          echo "🧪 Running library JUnit tests..."
          ./gradlew :react-native-view-shot:testDebugUnitTest --no-daemon --stacktrace

      - name: Build Android APK
        working-directory: example/android
        run: |
          echo "🔨 Building Android APK..."
          ./gradlew assembleDebug --no-daemon --stacktrace -PnewArchEnabled=true

      - name: Upload Android APK
        uses: actions/upload-artifact@v4
        with:
          name: android-apk
          path: example/android/app/build/outputs/apk/debug/app-debug.apk

  build-ios:
    name: Build iOS App
    runs-on: macos-15  # Use latest macOS with Xcode 16.1+
    needs: lint-and-type-check

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

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

      - name: Install dependencies
        run: npm install --legacy-peer-deps  # Need --legacy-peer-deps for react-native-windows@0.74.47

      - name: Build library
        run: npm run build

      - name: Install example dependencies
        working-directory: example
        run: npm ci --legacy-peer-deps

      - name: Setup CocoaPods
        uses: maxim-lobanov/setup-cocoapods@v1
        with:
          version: latest

      - name: Pod install
        working-directory: example/ios
        run: pod install --verbose

      - name: Build iOS
        working-directory: example
        run: |
          xcodebuild -workspace ios/ViewShotExample.xcworkspace \
            -scheme ViewShotExample \
            -configuration Debug \
            -destination 'generic/platform=iOS Simulator' \
            build CODE_SIGNING_ALLOWED=NO

      - name: Run iOS unit tests
        working-directory: example
        run: |
          # Pick the first available iPhone simulator and pass its UDID
          # (not its name) to xcodebuild. We use UDID because simulator
          # display names can contain parentheses (e.g. "iPhone SE
          # (3rd generation)"), and parsing them with a name-based regex
          # is brittle: stripping the trailing "(...)" loses version
          # disambiguation, and quoting parentheses on the
          # `-destination name=...` form is fragile across shells.
          # UDIDs are opaque and stable, so `id=$UDID` always resolves
          # exactly one device regardless of name shape. We also avoid
          # hard-coding e.g. "iPhone 16" which may disappear when Xcode
          # updates on macos-15.
          UDID=$(xcrun simctl list devices available -j | \
            jq -r '.devices | to_entries[]
              | select(.key | startswith("com.apple.CoreSimulator.SimRuntime.iOS"))
              | .value[]
              | select(.name | startswith("iPhone "))
              | .udid' | head -1)
          if [ -z "$UDID" ]; then
            echo "::error::No iPhone simulator available on the runner"
            xcrun simctl list devices available
            exit 1
          fi
          echo "Using simulator UDID: $UDID"
          xcrun simctl list devices available | grep "$UDID" || true
          xcodebuild test \
            -workspace ios/ViewShotExample.xcworkspace \
            -scheme ViewShotExampleTests \
            -destination "platform=iOS Simulator,id=$UDID" \
            CODE_SIGNING_ALLOWED=NO

      - name: Upload iOS Build Logs
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: ios-build-logs
          path: ~/Library/Logs/DiagnosticReports/

  test-windows-unit:
    name: Run Windows unit tests
    runs-on: ubuntu-latest
    needs: lint-and-type-check

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'

      - name: Cache NuGet packages
        uses: actions/cache@v4
        with:
          path: ~/.nuget/packages
          key: ${{ runner.os }}-nuget-${{ hashFiles('windows/RNViewShot.Tests/RNViewShot.Tests.csproj') }}
          restore-keys: ${{ runner.os }}-nuget-

      - name: Run dotnet tests
        run: |
          dotnet test windows/RNViewShot.Tests/RNViewShot.Tests.csproj \
            --nologo --verbosity normal \
            --logger "trx;LogFileName=test-results.trx" \
            --results-directory TestResults

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: windows-unit-test-results
          path: TestResults/

  build-windows:
    name: Build Windows
    runs-on: windows-latest
    needs: lint-and-type-check

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: |
            package-lock.json
            example-windows/package-lock.json

      - name: Setup MSBuild
        uses: microsoft/setup-msbuild@v2

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'

      - name: Ensure Windows SDK 10.0.22621
        shell: pwsh
        run: |
          $sdkPath = "C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0"
          if (Test-Path $sdkPath) {
            Write-Host "Windows SDK 10.0.22621 already installed"
            exit 0
          }
          Write-Host "Installed SDKs:"
          Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\Include" -ErrorAction SilentlyContinue | Select-Object Name | ForEach-Object { Write-Host "  $($_.Name)" }

          $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
          $info = & $vswhere -latest -prerelease -products * -format json | ConvertFrom-Json
          if (-not $info) { throw "vswhere returned no VS install" }
          Write-Host "VS productId: $($info.productId)"

          $vsInstaller = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vs_installer.exe"
          $args = @(
            'modify',
            '--channelId', $info.channelId,
            '--productId', $info.productId,
            '--add', 'Microsoft.VisualStudio.Component.Windows11SDK.22621',
            '--quiet', '--norestart'
          )
          Write-Host "Running: $vsInstaller $($args -join ' ')"
          $proc = Start-Process -FilePath $vsInstaller -ArgumentList $args -Wait -PassThru
          Write-Host "vs_installer exit code: $($proc.ExitCode)"

          if (-not (Test-Path $sdkPath)) {
            throw "Windows SDK 10.0.22621 still missing after install attempt"
          }
          Write-Host "Windows SDK 10.0.22621 installed"

      - name: Cache NuGet packages
        uses: actions/cache@v4
        with:
          path: ~/.nuget/packages
          key: ${{ runner.os }}-nuget-build-${{ hashFiles('**/packages.lock.json') }}
          restore-keys: ${{ runner.os }}-nuget-build-

      - name: Install root dependencies
        run: npm install --legacy-peer-deps

      - name: Build library
        run: npm run build

      - name: Install example-windows dependencies
        working-directory: example-windows
        run: npm ci --legacy-peer-deps

      - name: Run Windows autolinking
        working-directory: example-windows
        run: npx --no-install react-native autolink-windows --no-telemetry

      - name: Build example-windows
        working-directory: example-windows
        shell: pwsh
        run: |
          npx --no-install react-native run-windows `
            --arch x64 `
            --no-launch `
            --no-deploy `
            --no-packager `
            --no-autolink `
            --logging `
            --buildLogDirectory buildlogs `
            --no-telemetry

      - name: Verify build artifacts
        working-directory: example-windows
        shell: pwsh
        run: |
          $exe = "windows/ViewShotWindowsExample/bin/x64/Debug/ViewShotWindowsExample.exe"
          $rnviewshot = "windows/ViewShotWindowsExample/bin/x64/Debug/RNViewShot.winmd"
          if (-not (Test-Path $exe)) {
            Write-Error "Missing $exe"; exit 1
          }
          if (-not (Test-Path $rnviewshot)) {
            Write-Error "Missing $rnviewshot — RNViewShot module did not link"; exit 1
          }
          Write-Host "OK: $exe and RNViewShot.winmd present"

      - name: Upload Windows app package
        uses: actions/upload-artifact@v4
        with:
          name: windows-app
          path: example-windows/windows/ViewShotWindowsExample/AppPackages/**
          if-no-files-found: error

      - name: Upload Windows build logs
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: windows-build-logs
          path: |
            example-windows/buildlogs/**
            example-windows/windows/**/*.log
            example-windows/windows/**/*.err

  # Temporarily disabled - Android Detox setup needs native library conflict resolution
  # The Android build works fine, but Detox test APK has libfbjni.so conflicts
  # Using functional E2E tests instead (test-all-features.js)
  # test-e2e-android:
  #   name: E2E Tests (Android)
  #   runs-on: ubuntu-latest
  #   needs: build-android
  #
  #   steps:
  #     - name: Checkout code
  #       uses: actions/checkout@v4
  #
  #     - name: Setup Node.js
  #       uses: actions/setup-node@v4
  #       with:
  #         node-version: '20'
  #         cache: 'npm'
  #
  #     - name: Setup Java JDK
  #       uses: actions/setup-java@v4
  #       with:
  #         distribution: 'temurin'
  #         java-version: '17'
  #
  #     - name: Enable KVM group perms
  #       run: |
  #         echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
  #         sudo udevadm control --reload-rules
  #         sudo udevadm trigger --name-match=kvm
  #
  #     - name: Cache Gradle
  #       uses: actions/cache@v4
  #       with:
  #         path: |
  #           ~/.gradle/caches
  #           ~/.gradle/wrapper
  #         key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
  #         restore-keys: |
  #           ${{ runner.os }}-gradle-
  #
  #     - name: Cache AVD
  #       uses: actions/cache@v4
  #       id: avd-cache
  #       with:
  #         path: |
  #           ~/.android/avd/*
  #           ~/.android/adb*
  #         key: avd-34
  #
  #     - name: Create AVD and generate snapshot for caching
  #       if: steps.avd-cache.outputs.cache-hit != 'true'
  #       uses: reactivecircus/android-emulator-runner@v2
  #       with:
  #         api-level: 34
  #         target: google_apis
  #         arch: x86_64
  #         profile: Nexus 6
  #         force-avd-creation: false
  #         emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
  #         disable-animations: false
  #         script: echo "Generated AVD snapshot for caching."
  #
  #     - name: Install dependencies
  #       run: npm install --legacy-peer-deps
  #
  #     - name: Build library
  #       run: npm run build
  #
  #     - name: Install example dependencies
  #       working-directory: example
  #       run: npm ci --legacy-peer-deps
  #
  #     - name: Build Android APK for E2E
  #       working-directory: example/android
  #       run: ./gradlew assembleDebug
  #
  #     - name: Run Functional E2E Tests (Android)
  #       uses: reactivecircus/android-emulator-runner@v2
  #       with:
  #         api-level: 34
  #         target: google_apis
  #         arch: x86_64
  #         profile: Nexus 6
  #         force-avd-creation: false
  #         emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
  #         disable-animations: true
  #         script: |
  #           echo "🚀 Starting Functional E2E Tests for Android"
  #           cd example
  #           
  #           # Start Metro bundler in background
  #           npm start &
  #           METRO_PID=$!
  #           
  #           # Wait for Metro to start
  #           sleep 30
  #           
  #           # Run our comprehensive feature tests
  #           echo "📱 Running comprehensive ViewShot feature tests..."
  #           node test-all-features.js
  #           
  #           # Kill Metro
  #           kill $METRO_PID || true
  #
  #     - name: Upload E2E Test Results
  #       if: always()
  #       uses: actions/upload-artifact@v4
  #       with:
  #         name: e2e-test-results-android
  #         path: |
  #           example/test-*.png
  #           example/test-report.json


  test-detox-ios:
    name: Detox E2E Tests (iOS)
    runs-on: macos-15
    needs: build-ios

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

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

      - name: Install dependencies
        run: npm install --legacy-peer-deps

      - name: Install applesimutils for iOS Detox
        run: |
            # Try to install via brew tap first
            if brew tap wix/brew && brew install applesimutils 2>/dev/null; then
              echo "✅ Installed applesimutils via brew tap"
            else
              echo "⚠️  Brew install failed, trying manual compilation..."
              # Download source and compile
              curl -L https://github.com/wix/AppleSimulatorUtils/archive/0.9.6.tar.gz | tar -xz
              cd AppleSimulatorUtils-0.9.6
              xcodebuild -project applesimutils.xcodeproj -scheme applesimutils -configuration Release -derivedDataPath ./build clean build
              sudo cp ./build/Build/Products/Release/applesimutils /usr/local/bin/
              echo "✅ Installed applesimutils via compilation"
            fi

      - name: Build library
        run: npm run build

      - name: Install example dependencies
        working-directory: example
        run: npm ci --legacy-peer-deps

      - name: Setup CocoaPods
        uses: maxim-lobanov/setup-cocoapods@v1
        with:
          version: latest

      - name: Pod install
        working-directory: example/ios
        run: pod install --verbose

      - name: Build iOS for Detox
        working-directory: example
        run: npm run build:e2e:ios

      - name: Start Metro Bundler
        working-directory: example
        run: |
          npm start &
          echo $! > metro.pid
          sleep 30

      - name: Run Detox Tests
        working-directory: example
        run: npm run test:e2e:ios
        continue-on-error: true
        id: detox_ios_tests

      - name: Stop Metro Bundler
        if: always()
        working-directory: example
        run: |
          if [ -f metro.pid ]; then
            kill $(cat metro.pid) || true
            rm metro.pid
          fi

      - name: Upload Detox Screenshots
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: detox-screenshots-ios
          path: example/artifacts/**/*.png

      - name: Upload Detox Test Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: detox-test-results-ios
          path: |
            example/e2e/test-results/**/*
            example/artifacts/**/*.xml

      - name: Report iOS test results
        if: always()
        run: |
          # Note: Detox exit code is unreliable due to Jest SummaryReporter crash
          # (TypeError: Cannot read properties of undefined reading 'isSet')
          # Tests results are best checked via the uploaded artifacts and screenshots
          if [ "${{ steps.detox_ios_tests.outcome }}" == "failure" ]; then
            echo "⚠️ Detox exited with error (may be Jest reporter crash, check artifacts for actual results)"
          else
            echo "✅ Detox iOS tests completed"
          fi

  # TODO: Android Detox E2E tests disabled temporarily - not stable on CI.
  # Re-enable when Android emulator reliability is improved.
  # test-detox-android:
  #   name: Detox E2E Tests (Android)
  #   runs-on: ubuntu-latest
  #   needs: build-android
  #   timeout-minutes: 60

  build-web-example:
    name: Build Web Example
    runs-on: ubuntu-latest
    needs: lint-and-type-check

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install root dependencies
        run: npm install --legacy-peer-deps

      - name: Build library
        run: npm run build

      - name: Install web example dependencies
        working-directory: example-web
        run: npm ci

      - name: Build web example (production)
        working-directory: example-web
        run: npm run build

      - name: Verify build output
        working-directory: example-web
        run: |
          echo "✅ Web build completed successfully"
          ls -lh dist/
          du -sh dist/
          echo ""
          echo "Build contains:"
          ls -1 dist/

      - name: Upload web build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: web-build
          path: example-web/dist/
          retention-days: 7

      - name: Build summary
        working-directory: example-web
        run: |
          echo "### Web Build Summary 🌐" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "✅ Production build successful" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "**Bundle size:** $(du -sh dist/ | cut -f1)" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "**Files:**" >> $GITHUB_STEP_SUMMARY
          echo '```' >> $GITHUB_STEP_SUMMARY
          ls -lh dist/ >> $GITHUB_STEP_SUMMARY
          echo '```' >> $GITHUB_STEP_SUMMARY

  test-web-example:
    name: Test Web Example (Playwright)
    runs-on: ubuntu-latest
    needs: build-web-example

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install root dependencies
        run: npm install --legacy-peer-deps

      - name: Build library
        run: npm run build

      - name: Install web example dependencies
        working-directory: example-web
        run: npm ci

      - name: Install Playwright Browsers
        working-directory: example-web
        run: npx playwright install chromium --with-deps

      - name: Start web development server
        working-directory: example-web
        run: |
          npx webpack serve --mode development --port 3000 &
          echo $! > web-server.pid
          sleep 30
          # Wait for server to be ready
          for i in {1..30}; do
            if curl -s http://localhost:3000 > /dev/null; then
              echo "✅ Web server is ready"
              break
            fi
            echo "⏳ Waiting for web server... ($i/30)"
            sleep 2
          done
          
      - name: Run Playwright tests
        working-directory: example-web
        run: npm run test:e2e
        continue-on-error: true
        id: playwright_tests
        
      - name: Stop web development server
        if: always()
        working-directory: example-web
        run: |
          if [ -f web-server.pid ]; then
            kill $(cat web-server.pid) || true
            rm web-server.pid
          fi

      - name: Check for snapshot differences
        if: failure() && steps.playwright_tests.outcome == 'failure'
        working-directory: example-web
        run: |
          if [ -d "test-results" ]; then
            echo "## 📸 Visual Regression Detected" >> $GITHUB_STEP_SUMMARY
            echo "" >> $GITHUB_STEP_SUMMARY
            echo "Some visual snapshots have changed. This could be due to:" >> $GITHUB_STEP_SUMMARY
            echo "- UI changes in the web example" >> $GITHUB_STEP_SUMMARY
            echo "- Different rendering between platforms (Linux CI vs local macOS)" >> $GITHUB_STEP_SUMMARY
            echo "- Font or styling differences" >> $GITHUB_STEP_SUMMARY
            echo "" >> $GITHUB_STEP_SUMMARY
            echo "**To update snapshots:**" >> $GITHUB_STEP_SUMMARY
            echo "1. Download the \`web-snapshots-reference\` artifact" >> $GITHUB_STEP_SUMMARY
            echo "2. Replace \`example-web/e2e/snapshots/reference/\` with the downloaded files" >> $GITHUB_STEP_SUMMARY
            echo "3. Commit the updated snapshots" >> $GITHUB_STEP_SUMMARY
            echo "" >> $GITHUB_STEP_SUMMARY
            echo "**Diff files available in artifacts:**" >> $GITHUB_STEP_SUMMARY
            find test-results -name "*-diff.png" | head -5 | while read file; do
              echo "- \`$(basename "$file")\`" >> $GITHUB_STEP_SUMMARY
            done
          fi

      - name: Upload generated snapshots (actual from test-results)
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: web-snapshots-actual
          path: |
            example-web/test-results/**/*-actual.png
            example-web/test-results/**/*-expected.png
            example-web/test-results/**/*-diff.png
          retention-days: 30
          if-no-files-found: ignore

      - name: Upload Playwright report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: example-web/playwright-report/
          retention-days: 7

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-test-results
          path: example-web/test-results/
          retention-days: 7

      - name: Fail if tests failed
        if: steps.playwright_tests.outcome == 'failure'
        run: exit 1

