name: CI - Sanity Checks

on:
  push:
    branches:
      - main
  # Keep untrusted code on the read-only pull_request event. Never use pull_request_target here.
  pull_request:
    branches:
      - main
  workflow_dispatch:

env:
  # Only place this workflow names the SDK; .github/actions/setup-flutter-git pins the same release.
  FLUTTER_VERSION: "3.44.0"

jobs:
  analyze:
    name: Code Analysis
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false

      - name: Setup Flutter
        uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
        with:
          channel: "stable"
          flutter-version: ${{ env.FLUTTER_VERSION }}
          cache: true
          pub-cache: false

      - name: Cache Pub dependencies
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
        with:
          path: |
            ~/.pub-cache
          key: ${{ runner.os }}-pub-v3-${{ hashFiles('**/pubspec.yaml', '**/pubspec.lock') }}

      - name: Install dependencies
        run: |

          flutter pub get

      - name: Install wakelock_plus development dependencies
        working-directory: packages/wakelock_plus
        run: flutter pub get --enforce-lockfile --no-example

      - name: Verify generated files committed
        run: scripts/codegen.sh --check

      - name: Verify translation hygiene
        run: python3 scripts/clean_translations.py --check --strict

      - name: Verify workflow and script guards
        run: bash scripts/ci_guard_checks.sh

      - name: Verify formatting
        run: |
          paths=(lib)
          [ ! -d test ] || paths+=(test)
          find "${paths[@]}" -name "*.dart" ! -name "*.g.dart" ! -name "*.freezed.dart" -type f -print0 |
            xargs -0 -r dart format --output=none --set-exit-if-changed

      - name: Verify icon consistency
        run: dart run scripts/check_icon_consistency.dart

      - name: Analyze code
        run: dart run scripts/check_analyzer.dart

      - name: Check for unused code
        run: |
          echo "🔍 Checking for unused code..."
          dart run dart_code_linter:metrics check-unused-code lib 2>&1 | tee unused_code.txt
          if grep -qi "no unused code found" unused_code.txt; then
            echo "✅ No unused code found"
          else
            echo "❌ Found unused code:"
            cat unused_code.txt
            exit 1
          fi

      - name: Check for unused files
        run: |
          echo "🔍 Checking for unused files..."
          dart run dart_code_linter:metrics check-unused-files lib 2>&1 | tee unused_files.txt
          if grep -qi "no unused files found" unused_files.txt; then
            echo "✅ No unused files found"
          else
            echo "❌ Found unused files:"
            cat unused_files.txt
            exit 1
          fi

  test:
    name: Unit Tests
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false

      - name: Setup Flutter
        uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
        with:
          channel: "stable"
          flutter-version: ${{ env.FLUTTER_VERSION }}
          cache: true
          pub-cache: false

      - name: Cache Pub dependencies
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
        with:
          path: |
            ~/.pub-cache
          key: ${{ runner.os }}-pub-v3-${{ hashFiles('**/pubspec.yaml', '**/pubspec.lock') }}

      - name: Install dependencies
        run: |
          flutter clean
          flutter pub get

      - name: Run tests
        run: |
          if [ -d "test" ] && [ "$(find test -name '*_test.dart' | wc -l)" -gt 0 ]; then
            scripts/run_tests.sh
          else
            echo "No tests found, skipping test execution"
          fi

      - name: Install wakelock_plus test dependencies
        working-directory: packages/wakelock_plus
        run: flutter pub get --enforce-lockfile

      - name: Run wakelock_plus VM tests
        working-directory: packages/wakelock_plus
        run: flutter test test/wakelock_plus_linux_plugin_test.dart

      - name: Run wakelock_plus Chrome tests
        working-directory: packages/wakelock_plus
        run: flutter test --platform chrome --dart-define=WEB_PLUGIN_TESTS=true test/wakelock_plus_web_plugin_test.dart

  android-test:
    name: Android JVM and Native Tests
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false

      - name: Setup Java
        uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5
        with:
          distribution: "temurin"
          java-version: "21"

      - name: Setup Flutter
        uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
        with:
          channel: "stable"
          flutter-version: ${{ env.FLUTTER_VERSION }}
          cache: true
          pub-cache: false

      - name: Cache Pub dependencies
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
        with:
          path: |
            ~/.pub-cache
          key: ${{ runner.os }}-pub-v3-${{ hashFiles('**/pubspec.yaml', '**/pubspec.lock') }}

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

      - name: Install dependencies
        run: flutter pub get

      - name: Configure Android local properties
        run: printf 'flutter.sdk=%s\nsdk.dir=%s\n' "$FLUTTER_ROOT" "$ANDROID_HOME" > android/local.properties

      - name: Configure Android host native tests
        run: |
          cmake -S android/app/src/test/cpp -B build/android-host-tests \
            -DCMAKE_BUILD_TYPE=Debug

      - name: Build Android host native tests
        run: cmake --build build/android-host-tests --parallel 2

      - name: Run Android host native tests
        run: |
          ctest --test-dir build/android-host-tests \
            --output-on-failure --no-tests=error

      - name: Run Android JVM unit tests
        working-directory: android
        run: ./gradlew :app:testDebugUnitTest :saf_util:testDebugUnitTest :libass:testDebugUnitTest -x :app:compileFlutterBuildDebug --continue

      - name: Check Android API compatibility
        working-directory: android
        run: ./gradlew :app:lintDebug

  native-format:
    name: Native Formatting
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false

      - name: Setup Java
        uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5
        with:
          distribution: "temurin"
          java-version: "17"

      - name: Verify native formatting
        run: scripts/format_native.sh --check

      - name: Verify Linux native acquisition integrity
        run: bash linux/packaging/build-libmpv_test.sh

  linux-native-test:
    name: Linux native reliability (${{ matrix.sanitizer }})
    runs-on: ubuntu-latest
    permissions:
      contents: read
    strategy:
      fail-fast: false
      matrix:
        include:
          - sanitizer: address
            lifecycle_sanitizers: ON
          - sanitizer: thread
            lifecycle_sanitizers: OFF
    steps:
      - name: Checkout code
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false

      - name: Setup Flutter
        uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
        with:
          channel: "stable"
          flutter-version: ${{ env.FLUTTER_VERSION }}
          cache: true
          pub-cache: false

      - name: Install Linux native test dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends \
            clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev \
            libstdc++-12-dev libmpv-dev libepoxy-dev libcurl4-openssl-dev libevdev-dev

      - name: Prepare Flutter Linux configuration
        run: |
          flutter pub get --enforce-lockfile --no-example
          flutter build linux --debug --config-only --no-pub

      - name: Configure Linux native reliability tests
        run: |
          cmake -S linux -B build/linux-native-${{ matrix.sanitizer }} -G Ninja \
            -DCMAKE_BUILD_TYPE=Debug \
            -DPLEZY_BUILD_MPV_PLAYER_LIFECYCLE_TESTS=ON \
            -DPLEZY_MPV_LIFECYCLE_SANITIZERS=${{ matrix.lifecycle_sanitizers }} \
            -DPLEZY_BUILD_MPV_RELIABILITY_TESTS=ON \
            -DPLEZY_MPV_RELIABILITY_SANITIZER=${{ matrix.sanitizer }}

      - name: Build Linux native reliability tests
        run: |
          cmake --build build/linux-native-${{ matrix.sanitizer }} --parallel 2 --target \
            mpv_player_lifecycle_test \
            mpv_property_result_contract_test \
            mpv_gpu_bootstrap_test

      - name: Run Linux native reliability tests
        run: |
          ctest --test-dir build/linux-native-${{ matrix.sanitizer }} \
            --output-on-failure --no-tests=error

  apple-native-test:
    name: Apple native reliability (${{ matrix.platform }})
    runs-on: macos-26
    permissions:
      contents: read
    strategy:
      fail-fast: false
      matrix:
        include:
          - platform: iOS
            project_directory: ios
            workspace: ios/Runner.xcworkspace
            simulator_runtime: iOS
            simulator_platform: iOS
            static_destination: ""
          - platform: macOS
            project_directory: macos
            workspace: macos/Runner.xcworkspace
            simulator_runtime: ""
            simulator_platform: ""
            static_destination: platform=macOS
          - platform: tvOS
            project_directory: tvos
            workspace: tvos/Runner.xcworkspace
            simulator_runtime: tvOS
            simulator_platform: tvOS
            static_destination: ""
    steps:
      - name: Checkout code
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false

      - name: Setup Flutter
        uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
        with:
          channel: "stable"
          flutter-version: ${{ env.FLUTTER_VERSION }}
          cache: true
          pub-cache: false

      - name: Install locked Dart dependencies
        run: flutter pub get --enforce-lockfile --no-example

      - name: Record committed CocoaPods lockfile
        if: matrix.platform != 'tvOS'
        env:
          PODFILE_LOCK: ${{ matrix.project_directory }}/Podfile.lock
        run: |
          test -f "$PODFILE_LOCK"
          shasum -a 256 "$PODFILE_LOCK" > "$RUNNER_TEMP/plezy-podfile-lock.sha256"

      - name: Prepare iOS Flutter build settings
        if: matrix.platform == 'iOS'
        run: flutter build ios --config-only --simulator --debug --no-pub

      - name: Prepare macOS Flutter build settings
        if: matrix.platform == 'macOS'
        run: flutter build macos --config-only --debug --no-pub

      - name: Prepare tvOS Flutter engine
        if: matrix.platform == 'tvOS'
        run: tvos/scripts/fetch_engine.sh

      - name: Verify Flutter configuration preserved CocoaPods lockfile
        if: matrix.platform != 'tvOS'
        run: shasum -a 256 --check "$RUNNER_TEMP/plezy-podfile-lock.sha256"

      - name: Install locked CocoaPods dependencies
        if: matrix.platform != 'tvOS'
        working-directory: ${{ matrix.project_directory }}
        run: pod install --deployment

      - name: Install tvOS CocoaPods dependencies
        if: matrix.platform == 'tvOS'
        run: tvos/scripts/pod_install.sh

      - name: Verify tvOS project wiring
        if: matrix.platform == 'tvOS'
        run: ruby tvos/scripts/test_wire_mpv.rb

      - name: Select Apple test destination
        env:
          SIMULATOR_RUNTIME: ${{ matrix.simulator_runtime }}
          SIMULATOR_PLATFORM: ${{ matrix.simulator_platform }}
          STATIC_DESTINATION: ${{ matrix.static_destination }}
        run: |
          python3 - <<'PY'
          import json
          import os
          import subprocess

          destination = os.environ["STATIC_DESTINATION"]
          if not destination:
              runtime_name = os.environ["SIMULATOR_RUNTIME"]
              payload = json.loads(
                  subprocess.check_output(
                      ["xcrun", "simctl", "list", "devices", "available", "-j"],
                      text=True,
                  )
              )
              devices = [
                  device
                  for runtime, candidates in payload["devices"].items()
                  if f".{runtime_name}-" in runtime
                  for device in candidates
                  if device.get("isAvailable", False)
              ]
              if not devices:
                  raise SystemExit(f"no available {runtime_name} simulator")
              destination = (
                  f"platform={os.environ['SIMULATOR_PLATFORM']} Simulator,"
                  f"id={devices[0]['udid']}"
              )
          with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as output:
              output.write(f"APPLE_TEST_DESTINATION={destination}\n")
          PY

      - name: Run Apple native reliability tests
        run: |
          xcodebuild test \
            -workspace "${{ matrix.workspace }}" \
            -scheme Runner \
            -configuration Debug \
            -destination "$APPLE_TEST_DESTINATION" \
            -disableAutomaticPackageResolution \
            CODE_SIGNING_ALLOWED=NO \
            COMPILER_INDEX_STORE_ENABLE=NO

  windows-native-test:
    name: Windows native reliability (${{ matrix.arch }})
    runs-on: ${{ matrix.runner }}
    permissions:
      contents: read
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: x64
            runner: windows-latest
            flutter_setup: action
          - arch: arm64
            runner: windows-11-arm
            flutter_setup: git
    steps:
      - name: Checkout code
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false

      - name: Install 7-Zip
        if: matrix.arch == 'arm64'
        shell: pwsh
        run: choco install 7zip -y

      - name: Setup Flutter
        if: matrix.flutter_setup == 'action'
        uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
        with:
          channel: "stable"
          flutter-version: ${{ env.FLUTTER_VERSION }}
          cache: true
          pub-cache: false

      - name: Set up Flutter from its pinned commit
        if: matrix.flutter_setup == 'git'
        uses: ./.github/actions/setup-flutter-git

      - name: Install locked Dart dependencies
        shell: pwsh
        run: flutter pub get --enforce-lockfile --no-example

      - name: Install patched Flutter engine
        shell: pwsh
        run: |
          flutter precache --windows
          .\windows\tool\install-patched-engine.ps1

      - name: Prepare Flutter Windows configuration
        shell: pwsh
        run: flutter build windows --debug --config-only --no-pub

      - name: Configure Windows native reliability tests
        shell: pwsh
        run: |
          $buildDir = "build/windows/${{ matrix.arch }}"
          cmake -S windows -B $buildDir `
            -DPLEZY_BUILD_MPV_PROPERTY_CONTRACT_TESTS=ON `
            -DPLEZY_BUILD_DISPLAY_RECOVERY_TESTS=ON

      - name: Build Windows native reliability tests
        shell: pwsh
        run: |
          $buildDir = "build/windows/${{ matrix.arch }}"
          cmake --build $buildDir --config Debug --parallel 2 --target `
            mpv_property_result_contract_test `
            mpv_player_property_contract_test `
            display_mode_manager_test

      - name: Run Windows native reliability tests
        shell: pwsh
        run: |
          $buildDir = "build/windows/${{ matrix.arch }}"
          ctest --test-dir "$buildDir/runner" -C Debug --output-on-failure --no-tests=error

  dependency-check:
    name: Dependency Validation
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false

      - name: Setup Flutter
        uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
        with:
          channel: "stable"
          flutter-version: ${{ env.FLUTTER_VERSION }}
          cache: true
          pub-cache: false

      - name: Cache Pub dependencies
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
        with:
          path: |
            ~/.pub-cache
          key: ${{ runner.os }}-pub-v3-${{ hashFiles('**/pubspec.yaml', '**/pubspec.lock') }}

      - name: Verify dependencies
        run: |
          flutter clean
          flutter pub get
          flutter pub outdated

  server:
    name: Server checks
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false

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

      - name: Run server checks
        run: scripts/ci_server_checks.sh


  website:
    name: Website checks
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false

      - name: Setup Bun
        uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
        with:
          bun-version: "1.3.14"

      - name: Run website checks
        run: scripts/ci_website_checks.sh
