import type { ConfigContext, ExpoConfig } from 'expo/config'
import APP from './src/lib/branding.json'

export default ({ config }: ConfigContext): ExpoConfig => {
  // P2e — EAS Update code-signing guard. EAS Update is currently disabled
  // (no `updates` block below). The moment we turn OTA on, we MUST also
  // configure code signing per https://docs.expo.dev/eas-update/code-signing/
  // — without it, a compromised EAS account or any attacker with publish
  // access to our project ID can ship arbitrary JS to every install. This
  // assertion fails the build if a future commit ever sets `updates.url`
  // (or another EAS Update flag) without the matching `codeSigningCertificate`.
  const updatesConfig = (config as { updates?: Record<string, unknown> }).updates
  const otaActive = !!updatesConfig?.url || updatesConfig?.enabled === true
  if (otaActive && !updatesConfig?.codeSigningCertificate) {
    throw new Error(
      '[security/P2e] EAS Update is enabled but `updates.codeSigningCertificate` is not set. ' +
        'See https://docs.expo.dev/eas-update/code-signing/ before turning OTA on.'
    )
  }

  return {
    ...config,
    name: APP.name,
    slug: APP.slug,
    version: '1.0.3',
    orientation: 'portrait',
    icon: './assets/icon.png',
    scheme: APP.scheme,
    userInterfaceStyle: 'automatic',
    splash: {
      image: './assets/adaptive-icon.png',
      backgroundColor: '#000000',
    },
    assetBundlePatterns: ['**/*'],
    platforms: ['ios', 'android'],
    ios: {
      icon: './assets/AppIcon.icon',
      splash: {
        image: './assets/splash.png',
        resizeMode: 'contain',
        backgroundColor: '#000000',
      },
      userInterfaceStyle: 'automatic',
      supportsTablet: true,
      bundleIdentifier: APP.iosBundleId,
      // Paid Apple Developer team. Memory project_ios_signing.md: as of
      // 2026-05-04 the project signs cleanly under this team id with
      // automatic provisioning; the prior aps-environment strip workaround
      // is obsolete. Setting it here means `@bacons/apple-targets` flows
      // DEVELOPMENT_TEAM into the watch + complication targets without
      // each target's expo-target.config.json having to repeat the value.
      appleTeamId: 'ZWN6BP9FLY',
      // App Store Connect rejects duplicate buildNumbers per app. Bump on every
      // upload (`xcrun altool --upload-app` / Transporter). Under the no-EAS
      // posture this lives in source rather than EAS's remote counter.
      buildNumber: '30',
      // Universal Links (P2a). Declaring `applinks:halflig.ht` lets iOS
      // verify the apple-app-site-association file at
      // https://halflig.ht/.well-known/apple-app-site-association on
      // install and route matching https URLs (e.g. the OAuth callback at
      // https://halflig.ht/oauth/callback) directly into the app instead
      // of bouncing through Safari + the custom `halflight://` scheme. The
      // custom scheme remains the active OAuth callback (see AuthProvider.tsx
      // `REDIRECT_URI`) until the well-known file is confirmed served — the
      // declaration here is harmless without it (verification fails silently,
      // OS falls back to opening the URL in Safari). `halflig.ht` is the
      // owned domain; an earlier copy of this file referenced `halflight.app`
      // which is NOT under our control.
      associatedDomains: ['applinks:halflig.ht'],
      // App Group entitlement (Phase 1.2 / 3.2). Used by the Apple Watch
      // companion's shared-cache plan when it lands; declaring it on the iOS
      // production provisioning profile now means the watch target won't need
      // an iOS-side re-provisioning round when it ships. The group identifier
      // matches `branding.json` `appGroupId` — single source of truth for
      // both `app.config.ts` and the future `targets/watch/expo-target.config.json`.
      entitlements: {
        'com.apple.security.application-groups': [APP.appGroupId],
      },
      // Privacy Manifest (Phase 1.6). Apple has been auto-rejecting apps
      // without an adequate manifest since May 2024. Expo SDK 55 / RN 0.83
      // ship default manifests for autolinked modules; this block declares
      // the app-level required-reason API uses (MMKV touches NSUserDefaults
      // analogues, expo-file-system reads modification dates, transitive
      // mach_absolute_time, free-space checks for the storage screen) plus
      // the no-tracking / no-collection-via-SDKs claims. NSPrivacyCollected
      // intentionally empty — user-typed content sent to the user-chosen
      // instance is declared in the App Privacy nutrition label form on
      // ASC, not here (this block is for SDKs that collect; we have none).
      privacyManifests: {
        NSPrivacyAccessedAPITypes: [
          {
            NSPrivacyAccessedAPIType: 'NSPrivacyAccessedAPICategoryUserDefaults',
            NSPrivacyAccessedAPITypeReasons: ['CA92.1'],
          },
          {
            NSPrivacyAccessedAPIType: 'NSPrivacyAccessedAPICategoryFileTimestamp',
            NSPrivacyAccessedAPITypeReasons: ['C617.1'],
          },
          {
            NSPrivacyAccessedAPIType: 'NSPrivacyAccessedAPICategorySystemBootTime',
            NSPrivacyAccessedAPITypeReasons: ['35F9.1'],
          },
          {
            NSPrivacyAccessedAPIType: 'NSPrivacyAccessedAPICategoryDiskSpace',
            NSPrivacyAccessedAPITypeReasons: ['E174.1'],
          },
        ],
        NSPrivacyTracking: false,
        NSPrivacyTrackingDomains: [],
        NSPrivacyCollectedDataTypes: [],
      },
      infoPlist: {
        NSCameraUsageDescription: `${APP.name} uses the camera so you can take photos and record videos to post to your Pixelfed feed and share as stories. For example, tap the camera tab to capture a new photo and publish it as a post.`,
        NSMicrophoneUsageDescription: `${APP.name} uses the microphone to record audio with videos you capture in the app. For example, when you record a story video, the microphone captures the sound for that clip.`,
        // App uses only standard encryption (TLS, Keychain) — qualifies for the
        // exempt carve-out under U.S. encryption export rules. Declaring it
        // up-front skips the manual answer-the-prompt step on every App Store
        // submission. Re-run `npx expo prebuild` for the key to bake into
        // ios/Halflight/Info.plist.
        ITSAppUsesNonExemptEncryption: false,
      },
    },
    android: {
      adaptiveIcon: {
        foregroundImage: './assets/adaptive-icon.png',
        backgroundImage: './assets/adaptive-icon-background.png',
        monochromeImage: './assets/adaptive-icon-monochrome.png',
      },
      icon: './assets/icon.png',
      package: APP.androidPackage,
      // Play Console rejects non-monotonic versionCodes per package. Bump on
      // every AAB upload. Under the no-EAS posture this lives in source rather
      // than EAS's remote counter.
      versionCode: 8,
      permissions: [
        'android.permission.RECORD_AUDIO',
        'android.permission.CAMERA',
      ],
      // Strip READ_MEDIA_IMAGES injected by expo-screen-capture's manifest. The
      // permission is gated to API 33 only and only used by the lib's
      // `onScreenshot` event listener path; Halflight's actual usage
      // (`usePreventScreenCapture` + `enableAppSwitcherProtectionAsync`) is
      // FLAG_SECURE-based and needs no permission. Blocking it removes the
      // trigger for Play's Photo & Video Permissions declaration (which would
      // otherwise demand a video demo + review wait). Verified against
      // expo-image-picker's manifest (does NOT declare READ_MEDIA_IMAGES; uses
      // Play Services photo picker via ACTION_PICK_IMAGES).
      blockedPermissions: [
        // expo-media-library force-adds a broad read/storage set for its full
        // media API. We only WRITE (save) via MediaStore — which needs no read
        // permission on API 29+ — so strip every READ permission to stay off
        // Google Play's Photo & Video Permissions declaration. WRITE_EXTERNAL_STORAGE
        // (the legacy ≤API 28 save path) is intentionally kept.
        'android.permission.READ_MEDIA_IMAGES',
        'android.permission.READ_MEDIA_VIDEO',
        'android.permission.READ_EXTERNAL_STORAGE',
        'android.permission.READ_MEDIA_VISUAL_USER_SELECTED',
      ],
      softwareKeyboardLayoutMode: 'pan',
      userInterfaceStyle: 'automatic',
      // App Links (P2a). `autoVerify: true` makes Android fetch
      // https://halflig.ht/.well-known/assetlinks.json (signed with the
      // Play upload key per P0e) on install; matching https URLs route into
      // the app without the chooser dialog. Custom `halflight://` scheme
      // continues to handle the OAuth callback until the assetlinks file is
      // confirmed served and the SHA-256 fingerprint matches our upload key
      // — until then the autoVerify check fails and Android falls back to
      // the chooser dialog (which still works, just with a one-tap UX cost).
      // Host is `halflig.ht` (owned); an earlier copy referenced
      // `halflight.app`, which is NOT under our control.
      intentFilters: [
        {
          action: 'VIEW',
          autoVerify: true,
          data: [{ scheme: 'https', host: 'halflig.ht' }],
          category: ['BROWSABLE', 'DEFAULT'],
        },
      ],
    } as ExpoConfig['android'],
    web: {
      favicon: './assets/favicon.png',
    },
    plugins: [
      'expo-router',
      'expo-font',
      [
        'expo-share-intent',
        {
          iosActivationRules: {
            NSExtensionActivationSupportsWebURLWithMaxCount: 0,
            NSExtensionActivationSupportsWebPageWithMaxCount: 0,
            NSExtensionActivationSupportsImageWithMaxCount: 1,
            NSExtensionActivationSupportsMovieWithMaxCount: 0,
          },
          iosShareExtensionName: `${APP.name} Share`,
          androidIntentFilters: ['image/*'],
        },
      ],
      [
        'expo-image-picker',
        {
          photosPermission:
            'The app accesses your photos to let you share them with your friends.',
        },
      ],
      [
        'expo-media-library',
        {
          // Add-only save. `savePhotosPermission` emits NSPhotoLibraryAddUsageDescription
          // (the "…add to your Photos" prompt). We intentionally DON'T set
          // `photosPermission` — the full-access NSPhotoLibraryUsageDescription is owned
          // by expo-image-picker (the composer reads the library to pick uploads), and
          // forcing it off here would only fight that plugin. `granularPermissions: []`
          // stops media-library injecting Android READ_MEDIA_IMAGES/VIDEO/AUDIO — we only
          // write via MediaStore (no read permission needed on API 29+).
          savePhotosPermission: `${APP.name} adds photos and videos you save to your photo library.`,
          granularPermissions: [],
          isAccessMediaLocationEnabled: false,
        },
      ],
      [
        'expo-notifications',
        {
          mode: 'production',
          icon: './assets/notification-icon.png',
          color: '#ffffff',
        },
      ],
      [
        'expo-build-properties',
        {
          android: {
            enableProguardInReleaseBuilds: true,
            enableShrinkResourcesInReleaseBuilds: true,
          },
          ios: {
            useFrameworks: 'static',
          },
        },
      ],
      'expo-secure-store',
      'expo-image',
      'expo-web-browser',
      [
        // Android system navigation bar. With `enforceContrast: false` the OS
        // skips its translucent contrast scrim that, on Samsung One UI, made
        // a visible greyscale cutoff between our `$background` and the bar.
        // The bar then renders fully transparent in edge-to-edge mode and the
        // root window background (set via expo-system-ui in src/app/_layout.tsx)
        // shows through. `barStyle: 'dark'` is the cold-start glyph color
        // (dark buttons on the light theme's `#D8D8D8` background); the JS
        // effect re-applies the matching style whenever the user toggles
        // theme. `setStyle()` per the official docs requires `enforceContrast:
        // false` AND three-button navigation; gesture-nav users see no bar at
        // all, so the style is irrelevant for them.
        'expo-navigation-bar',
        {
          enforceContrast: false,
          barStyle: 'dark',
        },
      ],
      [
        'expo-video',
        {
          // Required for the per-player `staysActiveInBackground = true`
          // hand-off. Flipping this true causes prebuild to emit `audio`
          // in iOS `UIBackgroundModes`; pair with `applyAudioSession()`
          // in src/app/_layout.tsx so the session category is `playback`.
          // Only the *audible holder* (a deliberately-tapped feed cell or
          // the active story) actually opts into background; muted feed
          // cells leave `staysActiveInBackground: false` so backgrounding
          // a scrolling muted feed does not keep an army of buffer-pumps
          // alive.
          supportsBackgroundPlayback: true,
          supportsPictureInPicture: true,
        },
      ],
      [
        'expo-camera',
        {
          // Keep these strings in lock-step with the matching keys in
          // `ios.infoPlist` above. The expo-camera config plugin writes
          // NSCameraUsageDescription / NSMicrophoneUsageDescription into
          // Info.plist *after* the base `infoPlist` merge, so the plugin
          // values win — Apple App Review rejected v1.0.0+build.2 because
          // the plugin's old generic strings ("needs access to your camera
          // to capture stories") didn't sufficiently explain how the data
          // would be used. Apple's guidance: explain the use AND give a
          // concrete example.
          cameraPermission: `${APP.name} uses the camera so you can take photos and record videos to post to your Pixelfed feed and share as stories. For example, tap the camera tab to capture a new photo and publish it as a post.`,
          microphonePermission: `${APP.name} uses the microphone to record audio with videos you capture in the app. For example, when you record a story video, the microphone captures the sound for that clip.`,
          recordAudioAndroid: true,
        },
      ],
      'react-native-compressor',
      './plugins/withReleaseSigning',
      // Generates the watch target's mirrored Swift constants (branding,
      // tapestry letters, tapestry window math) into targets/watch/Generated/
      // BEFORE @bacons/apple-targets scans Swift sources. Order matters —
      // see plugins/withWatchGenerated.js for the rationale. Cheap (<10ms),
      // safe to re-run on every prebuild.
      './plugins/withWatchGenerated',
      // Apple-targets config plugin. Auto-discovers targets/*/expo-target.config.json
      // and synthesises Xcode project entries for each one. Must come AFTER
      // withWatchGenerated so the Generated/ Swift files exist when the
      // plugin globs *.swift in targets/watch/. Pinned to 4.0.6 (exact) —
      // ships the Watch/-subdirectory embed fix (PRs #178/#179) and the
      // REGISTER_APP_GROUPS fix for extensions with app groups (PR #181).
      '@bacons/apple-targets',
    ],
    experiments: {
      typedRoutes: true,
      reactCompiler: true,
    },
    extra: {
      router: {
        origin: false,
      },
      nodeEnv: 'production',
      eas: {
        projectId: '',
      },
    },
  }
}
