/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation
import UIKit

/**
 An `ApplicationDelegate` is designed to post-process the results from Facebook Login
 or Facebook Dialogs (or any action that requires switching over to the native Facebook
 app or Safari).

 The methods in this class are designed to mirror those in `UIApplicationDelegate`, and you
 should call them in the respective methods in your application delegate implementation.
 */
@objcMembers
@objc(FBSDKApplicationDelegate)
public final class ApplicationDelegate: NSObject {

  var applicationObservers = NSHashTable<FBSDKApplicationObserving>(options: .weakMemory)
  let components: CoreKitComponents
  let configurator: CoreKitConfiguring
  var isAppLaunched = false
  private var hasInitializeBeenCalled = false
  var applicationState = UIApplication.State.active {
    didSet {
      components.appEvents.setApplicationState(applicationState)
    }
  }

  private static let kitsBitmaskKey = "com.facebook.sdk.kits.bitmask"

  /// Gets the singleton instance.
  @objc(sharedInstance)
  public private(set) static var shared = ApplicationDelegate()

  override convenience init() {
    let components = CoreKitComponents.default
    self.init(components: components, configurator: CoreKitConfigurator(components: components))
  }

  init(components: CoreKitComponents, configurator: CoreKitConfiguring) {
    self.components = components
    self.configurator = configurator
    super.init()
  }

  /**
   Initializes the SDK.

   If you are using the SDK within the context of the `UIApplication` lifecycle, do not use this method.
   Instead use `application(_:didFinishLaunchingWithOptions:)`.

   As part of SDK initialization, basic auto logging of app events will occur, this can be
   controlled via the 'FacebookAutoLogAppEventsEnabled' key in your project's Info.plist file.
   */
  public func initializeSDK() {
    initializeSDK(launchOptions: [:])
  }

  func initializeSDK(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
    guard !hasInitializeBeenCalled else { return }

    hasInitializeBeenCalled = true

    // DO NOT MOVE THIS CALL
    // Dependencies MUST be configured before they are used
    configurator.performConfiguration()

    logInitialization()
    addObservers()
    components.appEvents.startObservingApplicationLifecycleNotifications()
    _ = application(UIApplication.shared, didFinishLaunchingWithOptions: launchOptions)
    handleDeferredActivationIfNeeded()
    enableInstrumentation()

    logBackgroundRefreshStatus()
    initializeAppLink()
    initializeAEMAutoSetup()

    configureSourceApplication(launchOptions: launchOptions)
  }

  private func initializeAppLink() {
    initializeMeasurementListener()
    logIfAutoAppLinkEnabled()
  }

  private func handleDeferredActivationIfNeeded() {
    // If SDK initialization is deferred until after the applicationDidBecomeActive notification is received,
    // then we need to manually perform this work in case it hasn't happened at all.
    if UIApplication.shared.applicationState == .active {
      applicationDidBecomeActive(nil)
    }
  }

  private func configureSourceApplication(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
    // Set the SourceApplication for time spent data. This is not going to update the value
    // if the app has already launched.
    components.appEvents.setSourceApplication(
      launchOptions?[.sourceApplication] as? String,
      open: launchOptions?[.url] as? URL
    )

    // Register on UIApplicationDidEnterBackgroundNotification events to reset source application data when app backgrounds.
    components.appEvents.registerAutoResetSourceApplication()
    components.internalUtility.validateFacebookReservedURLSchemes()
  }

  private func initializeMeasurementListener() {
    // Register Listener for App Link measurement events
    _MeasurementEventListener(eventLogger: components.appEvents, sourceApplicationTracker: components.appEvents)
      .registerForAppLinkMeasurementEvents()
  }

  private func initializeAEMAutoSetup() {
    guard
      #available(iOS 14.0, *)
    else {
      return
    }

    let flag = components.infoDictionaryProvider.fb_object(forInfoDictionaryKey: "FBSDKAemAutoSetupEnabled") as? Bool
    let enabled = flag ?? true
    if !enabled {
      components.aemManager.logAutoSetupStatus(false, source: "client_flag")
    }
    if enabled, components.featureChecker.isEnabled(.aemAutoSetup) {
      components.aemManager.enable(
        components.featureChecker.isEnabled(.aemAutoSetupProxy)
      )
    }
  }

  private func logBackgroundRefreshStatus() {
    components.backgroundEventLogger.logBackgroundRefreshStatus(UIApplication.shared.backgroundRefreshStatus)
  }

  private func logInitialization() {
    components.settings.logWarnings()
    components.settings.logIfSDKSettingsChanged()
    components.settings.recordInstall()
  }

  private func enableInstrumentation() {
    components.featureChecker.check(.instrument) { enabled in
      if enabled {
        _InstrumentManager.shared.enable()
      }
    }
  }

  private func addObservers() {
    components.notificationCenter.fb_addObserver(
      self,
      selector: #selector(applicationDidEnterBackground(_:)),
      name: UIApplication.didEnterBackgroundNotification,
      object: nil
    )
    components.notificationCenter.fb_addObserver(
      self,
      selector: #selector(applicationDidBecomeActive(_:)),
      name: UIApplication.didBecomeActiveNotification,
      object: nil
    )
    components.notificationCenter.fb_addObserver(
      self,
      selector: #selector(applicationWillResignActive(_:)),
      name: UIApplication.willResignActiveNotification,
      object: nil
    )

    addObserver(_BridgeAPI.shared)
  }

  // MARK: - UIApplicationDelegate-like interface

  /**
   Call this method from the `UIApplicationDelegate.application(_:continue:restorationHandler:)` method
   of your application delegate. It should be invoked in order to properly process the web URL (universal link)
   once the end user is redirected to your app.

   - Parameters:
     - application: The application as passed to `UIApplicationDelegate.application(_:continue:restorationHandler:).
     - userActivity: The user activity as passed to `UIApplicationDelegate.application(_:continue:restorationHandler:)`.

   - Returns: `true` if the URL was intended for the Facebook SDK, `false` if not.
   */
  @discardableResult
  @objc(application:continueUserActivity:)
  public func application(
    _ application: UIApplication,
    continue userActivity: NSUserActivity
  ) -> Bool {
    guard
      userActivity.activityType == NSUserActivityTypeBrowsingWeb,
      let url = userActivity.webpageURL
    else { return false }

    return self.application(application, open: url, options: [:])
  }

  /**
   Call this method from the `UIApplicationDelegate.application(_:open:options:)` method
   of your application delegate. It should be invoked for the proper processing of responses during interaction
   with the native Facebook app or Safari as part of an SSO authorization flow or Facebook dialogs.

   - Parameters:
     - application: The application as passed to `UIApplicationDelegate.application(_:open:options:)`.
     - url: The URL as passed to `UIApplicationDelegate.application(_:open:options:)`.
     - options: The options dictionary as passed to `UIApplicationDelegate.application(_:open:options:)`.

   - Returns: `true` if the URL was intended for the Facebook SDK, `false` if not.
   */
  @discardableResult
  @objc(application:openURL:options:)
  public func application(
    _ application: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any]
  ) -> Bool {
    self.application(
      application,
      open: url,
      sourceApplication: options[.sourceApplication] as? String,
      annotation: options[.annotation]
    )
  }

  /**
   Call this method from the `UIApplicationDelegate.application(_:openL:sourceApplication:annotation:)` method
   of your application delegate. It should be invoked for the proper processing of responses during interaction
   with the native Facebook app or Safari as part of an SSO authorization flow or Facebook dialogs.

   - Parameters:
     - application: The application as passed to `UIApplicationDelegate.application(_:open:sourceApplication:annotation:)`.
     - url: The URL as passed to `UIApplicationDelegate.application(_:open:sourceApplication:annotation:)`.
     - sourceApplication: The source application as passed to `UIApplicationDelegate.application(_:open:sourceApplication:annotation:)`.
     - annotation: The annotation as passed to `UIApplicationDelegate.application(_:open:sourceApplication:annotation:)`.

   - Returns: `true` if the URL was intended for the Facebook SDK, `false` if not.
   */
  @discardableResult
  @objc(application:openURL:sourceApplication:annotation:)
  public func application(
    _ application: UIApplication,
    open url: URL,
    sourceApplication: String?,
    annotation: Any?
  ) -> Bool {
    components.appEvents.setSourceApplication(sourceApplication, open: url)

    components.featureChecker.check(.AEM) { enabled in
      guard enabled else { return }

      let featureChecker = self.components.featureChecker

      AEMReporter.setCatalogMatchingEnabled(featureChecker.isEnabled(.aemCatalogMatching))
      AEMReporter.setConversionFilteringEnabled(featureChecker.isEnabled(.aemConversionFiltering))
      AEMReporter.setAdvertiserRuleMatchInServerEnabled(featureChecker.isEnabled(.aemAdvertiserRuleMatchInServer))
      AEMReporter.enable()
      AEMReporter.handle(url)
    }

    var handled = false
    applicationObservers.allObjects.forEach { observer in
      let observerHandled = observer.application?(
        application,
        open: url,
        sourceApplication: sourceApplication,
        annotation: annotation
      )

      if observerHandled ?? false {
        handled = true
      }
    }

    components.appEventsUtility.saveCampaignIDs(url)
    if handled {
      return true
    } else {
      logIfAppLinkEvent(url: url)
      return false
    }
  }

  // MARK: Finish Launching

  /**
   Call this method from the `UIApplicationDelegate.application(_:didFinishLaunchingWithOptions:)` method
   of your application delegate. It should be invoked for the proper use of the Facebook SDK.
   As part of SDK initialization, basic auto-logging of app events will occur; this can be
   controlled via the `FacebookAutoLogAppEventsEnabled` key in the project's Info.plist file.

   - Parameters:
     - application: The application as passed to `UIApplicationDelegate.application(_:didFinishLaunchingWithOptions:)`.
     - launchOptions: The launch options as passed to `UIApplicationDelegate.application(_:didFinishLaunchingWithOptions:)`.

   - Returns: `true` if there are any added application observers that themselves return true from calling `application(_:didFinishLaunchingWithOptions:)`.
     Otherwise will return `false`.

   - Note: If this method is called after calling `initializeSDK`, then the return value will always be `false`.
   */
  @discardableResult
  public func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    guard
      !isAppLaunched,
      !hasInitializeBeenCalled
    else { return false }

    initializeSDK(launchOptions: launchOptions)
    isAppLaunched = true

    initializeTokenCache()
    fetchServerConfiguration()

    if components.settings.isAutoLogAppEventsEnabled {
      logSDKInitialize()
    }

    initializeProfile()
    checkAuthentication()

    return notifyLaunchObservers(application: application, launchOptions: launchOptions)
  }

  private func initializeTokenCache() {
    components.accessTokenWallet.current = components.accessTokenWallet.tokenCache?.accessToken
  }

  private func fetchServerConfiguration() {
    components.serverConfigurationProvider.loadServerConfiguration(completionBlock: nil)
  }

  private func initializeProfile() {
    components.profileSetter.current = components.profileSetter.fetchCachedProfile()
  }

  private func checkAuthentication() {
    components.authenticationTokenWallet.current = components.authenticationTokenWallet.tokenCache?.authenticationToken
    _AuthenticationStatusUtility.checkAuthenticationStatus()
  }

  private func notifyLaunchObservers(
    application: UIApplication,
    launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    var someObserverHandledLaunch = false
    applicationObservers.allObjects.forEach { observer in
      let observerHandled = observer.application?(
        application,
        didFinishLaunchingWithOptions: launchOptions
      )

      if observerHandled ?? false {
        someObserverHandledLaunch = true
      }
    }

    return someObserverHandledLaunch
  }

  // MARK: Entering Background

  func applicationDidEnterBackground(_ notification: Notification) {
    applicationState = .background
    applicationObservers.allObjects.forEach { observer in
      observer.applicationDidEnterBackground?(notification.object as? UIApplication)
    }
  }

  func applicationDidBecomeActive(_ notification: Notification?) {
    applicationState = .active

    // Auto log basic events in case auto-logging of app events is enabled
    if components.settings.isAutoLogAppEventsEnabled {
      components.appEvents.activateApp()
    }

    components.skAdNetworkReporter?.checkAndRevokeTimer()

    applicationObservers.allObjects.forEach { observer in
      observer.applicationDidBecomeActive?(notification?.object as? UIApplication)
    }
  }

  func applicationWillResignActive(_ notification: Notification) {
    applicationState = .active
    applicationObservers.allObjects.forEach { observer in
      observer.applicationWillResignActive?(notification.object as? UIApplication)
    }
  }

  // MARK: - Internal Methods

  // MARK: FBSDKApplicationObserving

  /**
   Adds an observer that will be informed about application lifecycle events.

   - Note: Observers are weakly held
   */
  public func addObserver(_ observer: FBSDKApplicationObserving) {
    applicationObservers.add(observer)
  }

  /// Removes an observer so that it will no longer be informed about application lifecycle events.
  public func removeObserver(_ observer: FBSDKApplicationObserving) {
    applicationObservers.remove(observer)
  }

  private func logIfAppLinkEvent(url: URL) {
    guard let query = url.query else { return }

    let parameters = BasicUtility.dictionary(withQueryString: query)
    guard
      let appLinkDataString = parameters["al_applink_data"],
      let rawData = appLinkDataString.data(using: .utf8),
      let appLinkData = try? JSONSerialization.jsonObject(with: rawData) as? [String: Any]
    else { return }

    var logData = [AppEvents.ParameterName: Any]()

    let targetURLString = appLinkData["target_url"] as? String
    if let targetURL = targetURLString.flatMap(URL.init(string:)) {
      logData[.targetURL] = targetURL.absoluteString
      logData[.targetURLHost] = targetURL.host
    }

    if let referrerData = appLinkData["referer_data"] as? [String: Any] {
      logData[.referralTargetURL] = referrerData["target_url"]
      logData[.referralURL] = referrerData["url"]
      logData[.referralAppName] = referrerData["app_name"]
    }

    logData[.inputURL] = url.absoluteString
    logData[.inputURLScheme] = url.scheme

    components.appEvents.logInternalEvent(
      .appLinkInboundEvent,
      parameters: logData,
      isImplicitlyLogged: true
    )
  }

  func logSDKInitialize() {
    let frameworkFlagMap: [String: AppEvents.ParameterName] = [
      "FBSDKLoginManager": .isLoginLibraryIncluded,
      "FBSDKAutoLog": .isMarketingLibraryIncluded,
      "FBSDKMessengerButton": .isMessengerLibraryIncluded,
      "FBSDKPlacesManager": .isPlacesLibraryIncluded,
      "FBSDKShareDialog": .isShareLibraryIncluded,
      "FBSDKTVInterfaceFactory": .isTVLibraryIncluded,
    ]

    var bitmask = 0
    var bit = 0
    var parameters: [AppEvents.ParameterName: Any] = [.isCoreKitIncluded: 1]

    frameworkFlagMap.forEach { className, parameterName in
      if objc_lookUpClass(className) != nil {
        parameters[parameterName] = 1
        bitmask |= 1 << bit
      }

      bit += 1
    }

    let existingBitmask = components.defaultDataStore.fb_integer(forKey: Self.kitsBitmaskKey)
    if existingBitmask != bitmask {
      components.defaultDataStore.fb_setInteger(bitmask, forKey: Self.kitsBitmaskKey)
      components.appEvents.logInternalEvent(
        .initializeSDK,
        parameters: parameters,
        isImplicitlyLogged: false
      )
    }
  }

  private func logIfAutoAppLinkEnabled() {
    let enabled = components.infoDictionaryProvider.fb_object(forInfoDictionaryKey: "FBSDKAutoAppLinkEnabled") as? Bool
    if enabled ?? false {
      var parameters = [AppEvents.ParameterName: Any]()
      if !AppLinkUtility.isMatchURLScheme("fb\(components.settings.appID ?? "nil")") {
        let warning = "You haven't set the Auto App Link URL scheme: fb<YOUR APP ID>"
        parameters[.schemeWarning] = warning
        print(warning)
      }

      components.appEvents.logInternalEvent(
        .autoAppLink,
        parameters: parameters,
        isImplicitlyLogged: true
      )
    }
  }

  // MARK: - Testability

  #if DEBUG
  func resetHasInitializeBeenCalled() {
    hasInitializeBeenCalled = false
  }

  func resetApplicationObserverCache() {
    applicationObservers = NSHashTable<FBSDKApplicationObserving>(options: .weakMemory)
  }
  #endif
}

// swiftformat:disable:next extensionaccesscontrol
fileprivate extension AppEvents.Name {
  static let appLinkInboundEvent = Self("fb_al_inbound")
  static let autoAppLink = Self("fb_auto_applink")
}

// swiftformat:disable:next extensionaccesscontrol
fileprivate extension AppEvents.ParameterName {
  static let targetURL = Self("targetURL")
  static let targetURLHost = Self("targetURLHost")
  static let referralTargetURL = Self("referralTargetURL")
  static let referralURL = Self("referralURL")
  static let referralAppName = Self("referralAppName")
  static let inputURL = Self("inputURL")
  static let inputURLScheme = Self("inputURLScheme")
  static let isCoreKitIncluded = Self("core_lib_included")
  static let isLoginLibraryIncluded = Self("login_lib_included")
  static let isMarketingLibraryIncluded = Self("marketing_lib_included")
  static let isMessengerLibraryIncluded = Self("messenger_lib_included")
  static let isPlacesLibraryIncluded = Self("places_lib_included")
  static let isShareLibraryIncluded = Self("share_lib_included")
  static let isTVLibraryIncluded = Self("tv_lib_included")
  static let schemeWarning = Self("SchemeWarning")
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

import AuthenticationServices

typealias AuthenticationCompletionHandler = (URL?, Error?) -> Void

protocol AuthenticationSessionProtocol {
  init(
    url: URL,
    callbackURLScheme: String?,
    completionHandler: @escaping AuthenticationCompletionHandler
  )

  func start() -> Bool
  func cancel()

  @available(iOS 13.0, *)
  var presentationContextProvider: ASWebAuthenticationPresentationContextProviding? { get set }
}

extension ASWebAuthenticationSession: AuthenticationSessionProtocol {}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation

/// Specifies state of FBSDKAuthenticationSession
enum AuthenticationSessionState {
  /// There is no active authentication session
  case none
  /// The authentication session has started
  case started
  /// System dialog ("app wants to use facebook.com  to sign in")  to access facebook.com was presented to the user
  case showAlert
  /// Web browser with log in to authentication was presented to the user
  case showWebBrowser
  /// Authentication session was canceled by system. It happens when app goes to background while alert requesting access to facebook.com is presented
  case canceledBySystem
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation

@objc(FBSDKAuthenticationTokenClaims)
public final class AuthenticationTokenClaims: NSObject {

  private enum Keys {
    static let aud = "aud"
    static let email = "email"
    static let exp = "exp"
    static let familyName = "family_name"
    static let givenName = "given_name"
    static let iat = "iat"
    static let iss = "iss"
    static let jti = "jti"
    static let middleName = "middle_name"
    static let name = "name"
    static let nonce = "nonce"
    static let picture = "picture"
    static let sub = "sub"
    static let userAgeRange = "user_age_range"
    static let userBirthday = "user_birthday"
    static let userFriends = "user_friends"
    static let userGender = "user_gender"
    static let userHometown = "user_hometown"
    static let userLink = "user_link"
    static let userLocation = "user_location"
  }

  private enum Values {
    static let validHost = "facebook.com"
    static let validHostSuffix = ".facebook.com"
  }

  /// A unique identifier for the token.
  public let jti: String

  /// Issuer Identifier for the Issuer of the response.
  public let iss: String

  /// Audience(s) that this ID Token is intended for.
  public let aud: String

  /// String value used to associate a Client session with an ID Token, and to mitigate replay attacks.
  public let nonce: String

  /// Expiration time on or after which the ID Token MUST NOT be accepted for processing.
  public let exp: TimeInterval

  /// Time at which the JWT was issued.
  public let iat: TimeInterval

  /// Subject - Identifier for the End-User at the Issuer.
  public let sub: String

  /// End-User's full name in displayable form including all name parts.
  public let name: String?

  /// End-User's given name in displayable form
  public let givenName: String?

  /// End-User's middle name in displayable form
  public let middleName: String?

  /// End-User's family name in displayable form
  public let familyName: String?

  /**
   End-User's preferred e-mail address.

   IMPORTANT: This field will only be populated if your user has granted your application the 'email' permission.
   */
  public let email: String?

  /// URL of the End-User's profile picture.
  public let picture: String?

  /**
   End-User's friends.

   IMPORTANT: This field will only be populated if your user has granted your application the 'user_friends' permission.
   */
  public let userFriends: [String]?

  /// End-User's birthday
  public let userBirthday: String?

  /// End-User's age range
  public let userAgeRange: [String: NSNumber]?

  /// End-User's hometown
  public let userHometown: [String: String]?

  /// End-User's location
  public let userLocation: [String: String]?

  /// End-User's gender
  public let userGender: String?

  /// End-User's link
  public let userLink: String?

  /**
   Internal method exposed to facilitate transition to Swift.
   API Subject to change or removal without warning. Do not use.

   @warning INTERNAL - DO NOT USE
   */
  @objc(initWithEncodedClaims:nonce:)
  public convenience init?(
    encodedClaims: String,
    nonce expectedNonce: String
  ) {
    guard let claimsData = Base64.decode(asData: Base64.base64(fromBase64Url: encodedClaims)),
          let claimsDictionary = try? JSONSerialization.jsonObject(with: claimsData) as? [String: Any]
    else {
      return nil
    }

    let currentTime = Date().timeIntervalSince1970
    let maxTimeSinceTokenIssued: TimeInterval = 10 * 60 // 10 mins

    guard let validJTI = claimsDictionary[Keys.jti] as? String,
          !validJTI.isEmpty,
          // Validate issuer
          let issuer = claimsDictionary[Keys.iss] as? String,
          let issuerURL = URL(string: issuer),
          let host = issuerURL.host,
          host == Values.validHost || host.hasSuffix(Values.validHostSuffix),
          // Validate audience
          let dependencies = try? AuthenticationTokenClaims.getDependencies(),
          let audience = claimsDictionary[Keys.aud] as? String,
          audience == dependencies.settings.appID,
          // Validate expireation
          let expiration = claimsDictionary[Keys.exp] as? Double,
          expiration > currentTime,
          // Validate 'issued at' timestamp
          let issuedTimestamp = claimsDictionary[Keys.iat] as? Double,
          issuedTimestamp >= currentTime - maxTimeSinceTokenIssued,
          // Validate nonce
          let validNonce = claimsDictionary[Keys.nonce] as? String,
          !validNonce.isEmpty,
          validNonce == expectedNonce,
          // Validate subject
          let subject = claimsDictionary[Keys.sub] as? String,
          !subject.isEmpty
    else {
      return nil
    }

    var potentialAgeRange: [String: NSNumber]?
    if let rawAgeRange = claimsDictionary[Keys.userAgeRange] as? [String: NSNumber],
       !rawAgeRange.isEmpty {
      potentialAgeRange = rawAgeRange
    }

    var potentialHometown: [String: String]?
    if let hometown = claimsDictionary[Keys.userHometown] as? [String: String],
       !hometown.isEmpty {
      potentialHometown = hometown
    }

    var potentialLocation: [String: String]?
    if let location = claimsDictionary[Keys.userLocation] as? [String: String],
       !location.isEmpty {
      potentialLocation = location
    }

    self.init(
      jti: validJTI,
      iss: issuer,
      aud: audience,
      nonce: validNonce,
      exp: expiration,
      iat: issuedTimestamp,
      sub: subject,
      name: claimsDictionary[Keys.name] as? String,
      givenName: claimsDictionary[Keys.givenName] as? String,
      middleName: claimsDictionary[Keys.middleName] as? String,
      familyName: claimsDictionary[Keys.familyName] as? String,
      email: claimsDictionary[Keys.email] as? String,
      picture: claimsDictionary[Keys.picture] as? String,
      userFriends: claimsDictionary[Keys.userFriends] as? [String],
      userBirthday: claimsDictionary[Keys.userBirthday] as? String,
      userAgeRange: potentialAgeRange,
      userHometown: potentialHometown,
      userLocation: potentialLocation,
      userGender: claimsDictionary[Keys.userGender] as? String,
      userLink: claimsDictionary[Keys.userLink] as? String
    )
  }

  init(
    jti: String,
    iss: String,
    aud: String,
    nonce: String,
    exp: TimeInterval,
    iat: TimeInterval,
    sub: String,
    name: String?,
    givenName: String?,
    middleName: String?,
    familyName: String?,
    email: String?,
    picture: String?,
    userFriends: [String]?,
    userBirthday: String?,
    userAgeRange: [String: NSNumber]?,
    userHometown: [String: String]?,
    userLocation: [String: String]?,
    userGender: String?,
    userLink: String?
  ) {
    self.jti = jti
    self.iss = iss
    self.aud = aud
    self.nonce = nonce
    self.exp = exp
    self.iat = iat
    self.sub = sub
    self.name = name
    self.givenName = givenName
    self.middleName = middleName
    self.familyName = familyName
    self.email = email
    self.picture = picture
    self.userFriends = userFriends
    self.userBirthday = userBirthday
    self.userAgeRange = userAgeRange
    self.userHometown = userHometown
    self.userLocation = userLocation
    self.userGender = userGender
    self.userLink = userLink
  }
}

extension AuthenticationTokenClaims: DependentAsType {
  struct TypeDependencies {
    var settings: SettingsProtocol
  }

  static var configuredDependencies: TypeDependencies?

  static var defaultDependencies: TypeDependencies? = TypeDependencies(
    settings: Settings.shared
  )
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

import UIKit

/// A view to display a profile picture.
@objcMembers
@objc(FBSDKProfilePictureView)
public final class FBProfilePictureView: UIView {

  /// The mode for the receiver to determine the aspect ratio of the source image.
  public var pictureMode: Profile.PictureMode = .square {
    didSet {
      if pictureMode != oldValue {
        setNeedsImageUpdate()
      }
    }
  }

  /// The profile ID to show the picture for.
  public var profileID: String {
    get {
      profilePictureID
    }

    set {
      if newValue != profilePictureID {
        profilePictureID = newValue
        placeholderImageIsValid = false
        setNeedsImageUpdate()
      }
    }
  }

  var currentState: ProfilePictureViewState {
    let shouldImageFit = shouldImageFit()
    let screen = window?.screen ?? UIScreen.main
    let scale = screen.scale
    let imageSize = getImageSize(imageShouldFit: shouldImageFit, scale: scale)
    return ProfilePictureViewState(
      profileID: profileID,
      size: imageSize,
      scale: scale,
      pictureMode: pictureMode,
      imageShouldFit: shouldImageFit
    )
  }

  var hasProfileImage = false
  var lastState: ProfilePictureViewState?
  var needsImageUpdate = false
  var placeholderImageIsValid = false
  var imageView = UIImageView(frame: .zero)
  private var profilePictureID = "me"

  public override var bounds: CGRect {
    get {
      super.bounds
    }
    set {
      DispatchQueue.main.async {
        let currentBounds = newValue
        if currentBounds != self.bounds {
          super.bounds = self.bounds
          if currentBounds.size != self.bounds.size {
            self.placeholderImageIsValid = false
            self.setNeedsImageUpdate()
          }
        }
      }
    }
  }

  public override var contentMode: UIView.ContentMode {
    get {
      imageView.contentMode
    }
    set {
      if imageView.contentMode != newValue {
        imageView.contentMode = newValue
        super.contentMode = newValue
        setNeedsImageUpdate()
      }
    }
  }

  // MARK: - Initialization

  /**
   Create a new instance.

   - Parameter frame: Frame rectangle for the view.
   - Parameter profile: Optional profile to display a picture for.
   */
  @objc(initWith:profile:)
  public init(frame: CGRect, profile: Profile? = nil) {
    super.init(frame: frame)
    profilePictureID = profile?.userID ?? "me"
    setNeedsImageUpdate()
  }

  /**
   Create a new instance.

   - Parameter profile: Optional profile to display a picture for.
   */
  @objc(initWithProfile:)
  public convenience init(profile: Profile? = nil) {
    self.init(frame: .zero, profile: profile)
  }

  /**
   Initializes and returns a newly allocated view object with the specified frame rectangle.

   - Parameter frame: The frame rectangle for the view, measured in points. The origin of the frame is relative to the superview in which you plan to add it.
   This method uses the frame rectangle to set the center and bounds properties accordingly.
   */
  @objc(initWithFrame:)
  public override init(frame: CGRect) {
    super.init(frame: frame)
    performInitialConfiguration()
  }

  /// Initializes and returns a newly allocated view object from the specified coder.
  @objc(initWithCoder:)
  public required init?(coder: NSCoder) {
    super.init(coder: coder)
    performInitialConfiguration()
  }

  /**
   Explicitly marks the receiver as needing to update the image.

   This method is called whenever any properties that affect the source image are modified, but this can also
   be used to trigger a manual update of the image if it needs to be re-downloaded.
   */
  public func setNeedsImageUpdate() {
    DispatchQueue.main.async { [self] in

      // we can't do anything with an empty view, so just bail out until we have a size
      guard
        imageView.superview != nil,
        !bounds.isEmpty
      else { return }

      // ensure that we have an image. Do this here,
      // so we can draw the placeholder image synchronously if we don't have one
      if !placeholderImageIsValid,
         !hasProfileImage {
        setPlaceholderImage()
      }

      // debounce calls to needsImage against the main runloop
      if needsImageUpdate {
        return
      }

      needsImageUpdate = true
      updateImage()
    }
  }

  func performInitialConfiguration() {
    imageView = UIImageView(frame: bounds)
    imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    addSubview(imageView)
    backgroundColor = .white
    contentMode = .scaleAspectFit
    isUserInteractionEnabled = false
    NotificationCenter.default.addObserver(
      self,
      selector: #selector(accessTokenDidChange),
      name: .AccessTokenDidChange,
      object: nil
    )

    NotificationCenter.default.addObserver(
      self,
      selector: #selector(profileDidChange),
      name: .ProfileDidChange,
      object: nil
    )
  }

  func accessTokenDidChange(_ notification: Notification) {
    guard profileID == "me",
          notification.userInfo?[AccessTokenDidChangeUserIDKey] != nil
    else {
      return
    }

    lastState = nil
    updateImageWithAccessToken()
  }

  func profileDidChange(_ notification: Notification) {
    guard profileID == "me" else {
      return
    }

    lastState = nil
    updateImageWithProfile()
  }

  func updateImageWithAccessToken() {
    let state = currentState
    if lastState != state {
      setPlaceholderImage()
    }

    if state.profileID == "me",
       !AccessToken.isCurrentAccessTokenActive {
      return
    }

    lastState = state
    if let imageURL = getProfileImageURL(state: state) {
      fetchAndSetImage(with: imageURL, state: state)
    }
  }

  func updateImageWithProfile() {
    let state = currentState
    // if the current image is no longer representative of the current state, clear the current value out; otherwise,
    // leave the current value until the new resolution image is downloaded
    if lastState != state {
      setPlaceholderImage()
    }

    let profile = Profile.current
    guard state.profileID == "me" else {
      return
    }

    if let imageURL = profile?.imageURL {
      lastState = state
      fetchAndSetImage(with: imageURL, state: state)
    }
  }

  func fetchAndSetImage(with imageURL: URL, state: ProfilePictureViewState) {
    let request = URLRequest(url: imageURL)

    URLSession.shared.dataTask(with: request) { [weak self] data, _, error in
      guard error == nil,
            let data = data else {
        return
      }

      self?.updateImage(data: data, state: state)
    }
    .resume()
  }

  func shouldImageFit() -> Bool {
    switch contentMode {
    case
      .bottom,
      .bottomLeft,
      .bottomRight,
      .center,
      .left,
      .redraw,
      .right,
      .scaleAspectFit,
      .top,
      .topLeft,
      .topRight:
      return true
    case
      .scaleAspectFill,
      .scaleToFill:
      return false
    @unknown default:
      return false
    }
  }

  func getImageSize(imageShouldFit: Bool, scale: CGFloat) -> CGSize {
    // get the image size based on the contentMode and pictureMode
    var size = bounds.size
    switch pictureMode {
    case .square:
      var imageSize: CGFloat
      if imageShouldFit {
        imageSize = min(size.width, size.height)
      } else {
        imageSize = max(size.width, size.height)
      }
      size = CGSize(width: imageSize, height: imageSize)
    case .normal, .album, .small, .large:
      // use the bounds size
      break
    @unknown default:
      break
    }

    // adjust for the screen scale
    size = CGSize(width: size.width * scale, height: size.height * scale)

    return size
  }

  func getProfileImageURL(state: ProfilePictureViewState) -> URL? {
    // If there's an existing profile, use that profile's image url handler
    if let profile = Profile.current {
      return profile.imageURL(forMode: pictureMode, size: state.size)
    } else {
      return Profile.getImageURL(profileID: state.profileID, pictureMode: pictureMode, size: state.size)
    }
  }

  func setPlaceholderImage() {
    let fillColor = UIColor(red: 157 / 255, green: 177 / 255, blue: 204 / 255, alpha: 1)
    placeholderImageIsValid = true
    hasProfileImage = false

    DispatchQueue.main.async {
      self.imageView.image = _HumanSilhouetteIcon().image(size: self.imageView.bounds.size, color: fillColor)
    }
  }

  func updateImage(data: Data, state: ProfilePictureViewState) {
    guard state == lastState else { return }

    if let image = UIImage(data: data, scale: state.scale) {
      hasProfileImage = true
      DispatchQueue.main.async {
        self.imageView.image = image
      }
    } else {
      hasProfileImage = false
      placeholderImageIsValid = false
      setNeedsImageUpdate()
    }
  }

  func updateImage() {
    needsImageUpdate = false
    if profileID != "me" || AccessToken.isCurrentAccessTokenActive {
      updateImageWithAccessToken()
    } else if Profile.current?.imageURL != nil {
      updateImageWithProfile()
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

/**
 A factory for providing objects that conform to `KeychainStore`
 */
public final class KeychainStoreFactory: KeychainStoreProviding {

  public init() {}

  public func createKeychainStore(service: String, accessGroup: String?) -> KeychainStoreProtocol {
    KeychainStore(service: service, accessGroup: accessGroup)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation

/**
 Represents a Graph API permission.
 Each permission has its own set of requirements and suggested use cases.
 See a full list at https://developers.facebook.com/docs/facebook-login/permissions
 */
public enum Permission: Hashable, ExpressibleByStringLiteral {
  /// Provides access to a subset of items that are part of a person's public profile.
  case publicProfile
  /// Provides access the list of friends that also use your app.
  case userFriends
  /// Provides access to the person's primary email address.
  case email
  /// Provides access to a person's personal description (the 'About Me' section on their Profile)
  case userAboutMe
  /// Provides access to all common books actions published by any app the person has used.
  /// This includes books they've read, want to read, rated or quoted.
  case userActionsBooks
  /// Provides access to all common Open Graph fitness actions published by any app the person has used.
  /// This includes runs, walks and bikes actions.
  case userActionsFitness
  /// Provides access to all common Open Graph music actions published by any app the person has used.
  /// This includes songs they've listened to, and playlists they've created.
  case userActionsMusic
  /// Provides access to all common Open Graph news actions published by any app the person
  /// has used which publishes these actions.
  /// This includes news articles they've read or news articles they've published.
  case userActionsNews
  /// Provides access to all common Open Graph video actions published by any app the person
  /// has used which publishes these actions.
  case userActionsVideo
  /// Access the date and month of a person's birthday. This may or may not include the person's year of birth,
  /// dependent upon their privacy settings and the access token being used to query this field.
  case userBirthday
  /// Provides access to a person's education history through the education field on the User object.
  case userEducationHistory
  /// Provides read-only access to the Events a person is hosting or has RSVP'd to.
  case userEvents
  /// Provides access to read a person's game activity (scores, achievements) in any game the person has played.
  case userGamesActivity
  /// Provides access to a person's gender.
  case userGender
  /// Provides access to a person's hometown location through the hometown field on the User object.
  case userHometown
  /// Provides access to the list of all Facebook Pages and Open Graph objects that a person has liked.
  case userLikes
  /// Provides access to a person's current city through the location field on the User object.
  case userLocation
  /// Lets your app read the content of groups a person is an admin of through the Groups edge on the User object.
  case userManagedGroups
  /// Provides access to the photos a person has uploaded or been tagged in.
  case userPhotos
  /// Provides access to the posts on a person's Timeline. Includes their own posts, posts they are tagged in,
  /// and posts other people make on their Timeline.
  case userPosts
  /// Provides access to a person's relationship status,
  /// significant other and family members as fields on the User object.
  case userRelationships
  /// Provides access to a person's relationship interests as the interested_in field on the User object.
  case userRelationshipDetails
  /// Provides access to a person's religious and political affiliations.
  case userReligionPolitics
  /// Provides access to the Places a person has been tagged at in photos, videos, statuses and links.
  case userTaggedPlaces
  /// Provides access to the videos a person has uploaded or been tagged in.
  case userVideos
  /// Provides access to the person's personal website URL via the website field on the User object.
  case userWebsite
  /// Provides access to a person's work history and list of employers via the work field on the User object.
  case userWorkHistory
  /// Provides access to the names of custom lists a person has created to organize their friends.
  case readCustomFriendlists
  /// Provides read-only access to the Insights data for Pages, Apps and web domains the person owns.
  case readInsights
  /// Provides read-only access to the Audience Network Insights data for Apps the person owns.
  case readAudienceNetworkInsights
  /// Provides the ability to read from the Page Inboxes of the Pages managed by a person.
  case readPageMailboxes
  /// Provides the access to show the list of the Pages that you manage.
  case pagesShowList
  /// Provides the access to manage call to actions of the Pages that you manage.
  case pagesManageCta
  /// Lets your app manage Instant Articles on behalf of Facebook Pages administered by people using your app.
  case pagesManageInstantArticles
  /// Provides the access to Ads Insights API to pull ads report information for ad accounts you have access to.
  case adsRead
  /// Provides access to the Facebook profile URL of the person using your app.
  case userLink
  /// Provides access a person's age range as listed in their Facebook profile.
  case userAgeRange
  /**
   Permission with a custom string value.
   See https://developers.facebook.com/docs/facebook-login/permissions for full list of available permissions.
   */
  case custom(String)

  public init(stringLiteral value: String) {
    guard let permission = StringPermission(rawValue: value)?.permission else {
      self = .custom(value)
      return
    }

    self = permission
  }

  public var name: String {
    if let permission = stringPermission {
      return permission.rawValue
    }

    if case let .custom(permission) = self {
      return permission
    }

    return ""
  }

  private var stringPermission: StringPermission? {
    switch self {
    case .publicProfile: return .publicProfile
    case .userFriends: return .userFriends
    case .email: return .email
    case .userAboutMe: return .userAboutMe
    case .userActionsBooks: return .userActionsBooks
    case .userActionsFitness: return .userActionsFitness
    case .userActionsMusic: return .userActionsMusic
    case .userActionsNews: return .userActionsNews
    case .userActionsVideo: return .userActionsVideo
    case .userBirthday: return .userBirthday
    case .userEducationHistory: return .userEducationHistory
    case .userEvents: return .userEvents
    case .userGamesActivity: return .userGamesActivity
    case .userGender: return .userGender
    case .userHometown: return .userHometown
    case .userLikes: return .userLikes
    case .userLocation: return .userLocation
    case .userManagedGroups: return .userManagedGroups
    case .userPhotos: return .userPhotos
    case .userPosts: return .userPosts
    case .userRelationships: return .userRelationships
    case .userRelationshipDetails: return .userRelationshipDetails
    case .userReligionPolitics: return .userReligionPolitics
    case .userTaggedPlaces: return .userTaggedPlaces
    case .userVideos: return .userVideos
    case .userWebsite: return .userWebsite
    case .userWorkHistory: return .userWorkHistory
    case .readCustomFriendlists: return .readCustomFriendlists
    case .readInsights: return .readInsights
    case .readAudienceNetworkInsights: return .readAudienceNetworkInsights
    case .readPageMailboxes: return .readPageMailboxes
    case .pagesShowList: return .pagesShowList
    case .pagesManageCta: return .pagesManageCta
    case .pagesManageInstantArticles: return .pagesManageInstantArticles
    case .adsRead: return .adsRead
    case .userLink: return .userLink
    case .userAgeRange: return .userAgeRange
    case .custom: return nil
    }
  }
}

private enum StringPermission: String {
  case publicProfile = "public_profile"
  case userFriends = "user_friends"
  case email
  case userAboutMe = "user_about_me"
  case userActionsBooks = "user_actions.books"
  case userActionsFitness = "user_action.fitness"
  case userActionsMusic = "user_actions.music"
  case userActionsNews = "user_actions.news"
  case userActionsVideo = "user_actions.video"
  case userBirthday = "user_birthday"
  case userEducationHistory = "user_education_history"
  case userEvents = "user_events"
  case userGamesActivity = "user_games_activity"
  case userGender = "user_gender"
  case userHometown = "user_hometown"
  case userLikes = "user_likes"
  case userLocation = "user_location"
  case userManagedGroups = "user_managed_groups"
  case userPhotos = "user_photos"
  case userPosts = "user_posts"
  case userRelationships = "user_relationships"
  case userRelationshipDetails = "user_relationship_details"
  case userReligionPolitics = "user_religion_politics"
  case userTaggedPlaces = "user_tagged_places"
  case userVideos = "user_videos"
  case userWebsite = "user_website"
  case userWorkHistory = "user_work_history"
  case readCustomFriendlists = "read_custom_friendlists"
  case readInsights = "read_insights"
  case readAudienceNetworkInsights = "read_audience_network_insights"
  case readPageMailboxes = "read_page_mailboxes"
  case pagesShowList = "pages_show_list"
  case pagesManageCta = "pages_manage_cta"
  case pagesManageInstantArticles = "pages_manage_instant_articles"
  case adsRead = "ads_read"
  case userLink = "user_link"
  case userAgeRange = "user_age_range"

  var permission: Permission {
    switch self {
    case .publicProfile: return .publicProfile
    case .userFriends: return .userFriends
    case .email: return .email
    case .userAboutMe: return .userAboutMe
    case .userActionsBooks: return .userActionsBooks
    case .userActionsFitness: return .userActionsFitness
    case .userActionsMusic: return .userActionsMusic
    case .userActionsNews: return .userActionsNews
    case .userActionsVideo: return .userActionsVideo
    case .userBirthday: return .userBirthday
    case .userEducationHistory: return .userEducationHistory
    case .userEvents: return .userEvents
    case .userGamesActivity: return .userGamesActivity
    case .userGender: return .userGender
    case .userHometown: return .userHometown
    case .userLikes: return .userLikes
    case .userLocation: return .userLocation
    case .userManagedGroups: return .userManagedGroups
    case .userPhotos: return .userPhotos
    case .userPosts: return .userPosts
    case .userRelationships: return .userRelationships
    case .userRelationshipDetails: return .userRelationshipDetails
    case .userReligionPolitics: return .userReligionPolitics
    case .userTaggedPlaces: return .userTaggedPlaces
    case .userVideos: return .userVideos
    case .userWebsite: return .userWebsite
    case .userWorkHistory: return .userWorkHistory
    case .readCustomFriendlists: return .readCustomFriendlists
    case .readInsights: return .readInsights
    case .readAudienceNetworkInsights: return .readAudienceNetworkInsights
    case .readPageMailboxes: return .readPageMailboxes
    case .pagesShowList: return .pagesShowList
    case .pagesManageCta: return .pagesManageCta
    case .pagesManageInstantArticles: return .pagesManageInstantArticles
    case .adsRead: return .adsRead
    case .userLink: return .userLink
    case .userAgeRange: return .userAgeRange
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation

extension Profile {
  /// Defines the aspect ratio mode for the source image of the profile picture.
  @objc(FBSDKProfilePictureMode)
  public enum PictureMode: UInt {
    /// A square cropped version of the image will be included in the view.
    case square

    /// The original picture's aspect ratio will be used for the source image in the view.
    case normal

    /// The original picture's aspect ratio will be used for the source image in the view.
    case album

    /// The original picture's aspect ratio will be used for the source image in the view.
    case small

    /// The original picture's aspect ratio will be used for the source image in the view.
    case large
  }

  /**
   A convenience method for returning a complete `URL` for retrieving the user's profile image.

   - Parameters:
     - pictureMode: The picture mode.
     - size: The height and width. This will be rounded to integer precision.
   */
  @objc(imageURLForPictureMode:size:)
  public func imageURL(forMode pictureMode: PictureMode, size: CGSize) -> URL? {
    Self.getImageURL(profileID: userID, pictureMode: pictureMode, size: size)
  }

  static func getImageURL(
    profileID: String,
    pictureMode: PictureMode,
    size: CGSize
  ) -> URL? {
    guard let dependencies = try? Self.getDependencies() else { return nil }

    var queryItems: [ImageURL.QueryItemName: String] = [
      .pictureMode: ImageURL.PictureMode(mode: pictureMode).rawValue,
      .width: String(Int(size.width)),
      .height: String(Int(size.height)),
    ]

    if let token = dependencies.accessTokenProvider.current {
      queryItems[.accessToken] = token.tokenString
    } else if let token = dependencies.settings.clientToken {
      queryItems[.accessToken] = token
    } else {
      print(
        """
        As of Graph API v8.0, profile images may not be retrieved without a token. This can be the current access \
        token from logging in with Facebook or it can be set via the plist or in code. Providing neither will cause \
        this call to return a silhouette image.
        """
      )
    }

    let stringlyKeyedItems = queryItems.reduce(into: [String: String]()) { values, item in
      values[item.key.rawValue] = item.value
    }

    return try? dependencies.urlHoster.facebookURL(
      hostPrefix: ImageURL.hostPrefix,
      path: "\(profileID)/\(ImageURL.path)",
      queryParameters: stringlyKeyedItems
    )
  }

  private enum ImageURL {
    static let hostPrefix = "graph"
    static let path = "picture"

    enum QueryItemName: String {
      case accessToken = "access_token"
      case pictureMode = "type"
      case width
      case height
    }

    enum PictureMode: String {
      case normal
      case square
      case small
      case album
      case large

      init(mode: Profile.PictureMode) {
        switch mode {
        case .normal: self = .normal
        case .square: self = .square
        case .small: self = .small
        case .album: self = .album
        case .large: self = .large
        }
      }
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation

extension Profile {

  private static let encodedDateFormat = "MM/dd/yyyy"
  private static let dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = encodedDateFormat
    return formatter
  }()

  /**
   Loads the current profile and passes it to the completion block.

   - Parameter: completion The block to be executed once the profile is loaded.

   - Note: If the profile is already loaded, this method will call the completion block synchronously, otherwise it
   will begin a graph request to update `current` and then call the completion block when finished.
   */
  @objc(loadCurrentProfileWithCompletion:)
  public static func loadCurrentProfile(completion: ProfileBlock?) {
    let dependencies: TypeDependencies
    do {
      dependencies = try getDependencies()
    } catch {
      completion?(nil, error)
      return
    }

    return loadProfile(accessToken: dependencies.accessTokenProvider.current, completion: completion)
  }

  private static var runningConnection: GraphRequestConnecting?

  private var isExpired: Bool {
    Date().timeIntervalSince(refreshDate) > Self.expirationInterval
  }

  static func loadProfile(accessToken: AccessToken?, completion: ProfileBlock?) {
    let dependencies: TypeDependencies
    do {
      dependencies = try getDependencies()
    } catch {
      completion?(nil, error)
      return
    }

    let request = dependencies.graphRequestFactory.createGraphRequest(
      withGraphPath: URLValues.path,
      parameters: makeGraphRequestParameters(token: accessToken),
      flags: [.doNotInvalidateTokenOnError, .disableErrorRecovery]
    )

    let isCurrentProfileExpired = current?.isExpired ?? false

    guard
      let token = accessToken,
      isCurrentProfileExpired || (current?.userID != token.userID) || (current?.isLimited ?? false)
    else {
      completion?(current, nil)
      return
    }

    let capturedProfile = current
    runningConnection?.cancel()

    runningConnection = request.start { _, result, error in
      guard capturedProfile === current else {
        completion?(nil, nil)
        return
      }

      guard
        let response = result,
        error == nil
      else {
        current = nil
        completion?(nil, error)
        return
      }

      let profile = parseLoadResponse(response)
      current = profile
      completion?(profile, nil)
    }
  }

  private static func parseLoadResponse(_ response: Any) -> Profile? {
    guard
      let response = response as? [String: Any],
      let identifier = response[ResponseKey.identifier.rawValue] as? String,
      !identifier.isEmpty
    else { return nil }

    let rawLinkURL = response[ResponseKey.link.rawValue] as? String
    let linkURL = (response[ResponseKey.link.rawValue] as? URL) ?? (rawLinkURL.flatMap(URL.init(string:)))

    let friendsResponse = response[ResponseKey.friends.rawValue] as? [String: Any]
    let friends = friendsResponse.flatMap(friendIdentifiers(from:))

    let ageRangeResponse = response[ResponseKey.ageRange.rawValue] as? [String: NSNumber]
    let ageRange = ageRangeResponse.flatMap(UserAgeRange.init(from:))

    let rawBirthday = response[ResponseKey.birthday.rawValue] as? String
    let birthday = rawBirthday.flatMap { dateFormatter.date(from: $0) }
    let rawHometown = response[ResponseKey.hometown.rawValue] as? [String: String]
    let hometown = rawHometown.flatMap(Location.init(from:))
    let rawLocation = response[ResponseKey.location.rawValue] as? [String: String]
    let location = rawLocation.flatMap(Location.init(from:))
    let gender = response[ResponseKey.gender.rawValue] as? String

    return Self(
      userID: identifier,
      firstName: response[ResponseKey.firstName.rawValue] as? String,
      middleName: response[ResponseKey.middleName.rawValue] as? String,
      lastName: response[ResponseKey.lastName.rawValue] as? String,
      name: response[ResponseKey.name.rawValue] as? String,
      linkURL: linkURL,
      refreshDate: Date(),
      imageURL: nil,
      email: response[ResponseKey.email.rawValue] as? String,
      friendIDs: friends,
      birthday: birthday,
      ageRange: ageRange,
      hometown: hometown,
      location: location,
      gender: gender
    )
  }

  static func makeGraphRequestParameters(token: AccessToken?) -> [String: String] {
    let fields = getGraphFields(token: token)
      .map(\.rawValue)
      .joined(separator: URLValues.fieldSeparator)

    return [URLValues.fieldsItemName: fields]
  }

  private static func getGraphFields(token: AccessToken?) -> [Field] {
    let primaryFields: [Field] = [.identifier, .firstName, .middleName, .lastName, .fullName]
    guard let token = token else { return primaryFields }

    let secondaryFieldsByPermission: [Permission: Field] = [
      .userLink: .link,
      .email: .email,
      .userFriends: .friends,
      .userBirthday: .birthday,
      .userAgeRange: .ageRange,
      .userHometown: .hometown,
      .userLocation: .location,
      .userGender: .gender,
    ]

    let secondaryFields = secondaryFieldsByPermission.compactMap { permission, field in
      token.permissions.contains(permission) ? field : nil
    }

    return primaryFields + secondaryFields
  }

  private static func friendIdentifiers(from response: [String: Any]) -> [UserIdentifier]? {
    guard let rawIdentifiers = response[ResponseKey.data.rawValue] as? [[String: Any]] else { return nil }

    let identifiers = rawIdentifiers.compactMap { $0[ResponseKey.identifier.rawValue] as? UserIdentifier }
    return identifiers.isEmpty ? nil : identifiers
  }

  private enum ResponseKey: String {
    case identifier = "id"
    case link
    case friends
    case ageRange = "age_range"
    case birthday
    case hometown
    case location
    case gender
    case firstName = "first_name"
    case middleName = "middle_name"
    case lastName = "last_name"
    case name
    case email
    case data
  }

  private enum URLValues {
    static let path = "me"
    static let fieldsItemName = "fields"
    static let fieldSeparator = ","
  }

  private enum Field: String {
    case identifier = "id"
    case firstName = "first_name"
    case middleName = "middle_name"
    case lastName = "last_name"
    case fullName = "name"
    case link
    case email
    case friends
    case birthday
    case ageRange = "age_range"
    case hometown
    case location
    case gender
  }

  private static let expirationInterval = TimeInterval(
    60 /* seconds */ * 60 /* minutes */ * 24 /* hours */
  ) /* one day in seconds */
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation

extension Profile: ProfileProviding {
  /// The current profile.
  @objc(currentProfile)
  public static var current: Profile? {
    get { _current }
    set {
      guard
        newValue != nil || _current != nil,
        newValue !== _current
      else { return }

      if let old = _current,
         let new = newValue,
         new.hasEqualProperties(to: old) {
        return
      } else {
        let old = _current
        _current = newValue
        Self.cacheProfile(newValue)
        postChangeNotification(old: old, new: newValue)
      }
    }
  }

  private func hasEqualProperties(to other: Profile) -> Bool {
    userID == other.userID
      && firstName == other.firstName
      && middleName == other.middleName
      && lastName == other.lastName
      && name == other.name
      && linkURL == other.linkURL
      && refreshDate == other.refreshDate
      && imageURL == other.imageURL
      && email == other.email
      && friendIDs == other.friendIDs
      && isLimited == other.isLimited
      && birthday == other.birthday
      && ageRange == other.ageRange
      && hometown == other.hometown
      && location == other.location
      && gender == other.gender
  }

  public static func fetchCachedProfile() -> Self? {
    guard let data = Self.dataStore?.fb_object(forKey: profileUserDefaultsKey) as? Data else {
      return nil
    }

    let unarchiver = UnarchiverProvider.createSecureUnarchiver(for: data)
    return unarchiver.decodeObject(of: Profile.self, forKey: NSKeyedArchiveRootObjectKey) as? Self
  }

  private static func cacheProfile(_ profile: Profile?) {
    guard
      let profile = profile,
      let data = try? NSKeyedArchiver.archivedData(withRootObject: profile, requiringSecureCoding: false)
    else {
      // swiftformat:disable:next redundantSelf
      self.dataStore?.fb_removeObject(forKey: profileUserDefaultsKey)
      return
    }

    // swiftformat:disable:next redundantSelf
    self.dataStore?.fb_setObject(data, forKey: profileUserDefaultsKey)
  }

  private static func postChangeNotification(old: Profile?, new: Profile?) {
    Self.notificationCenter?.fb_post(
      name: .ProfileDidChange,
      object: Self.self,
      userInfo: [
        ProfileChangeNewKey: new,
        ProfileChangeOldKey: old,
      ].compactMapValues { $0 }
    )
  }
}

extension Profile: NSSecureCoding {
  static let profileUserDefaultsKey = "com.facebook.sdk.FBSDKProfile.currentProfile"

  public static var supportsSecureCoding: Bool { true }

  public convenience init?(coder decoder: NSCoder) {
    guard let identifier = decoder.decodeObject(
      of: NSString.self,
      forKey: CodingKeys.userID.rawValue
    ) as? UserIdentifier
    else { return nil }

    let firstName = decoder.decodeObject(of: NSString.self, forKey: CodingKeys.firstName.rawValue)
    let middleName = decoder.decodeObject(of: NSString.self, forKey: CodingKeys.middleName.rawValue)
    let lastName = decoder.decodeObject(of: NSString.self, forKey: CodingKeys.lastName.rawValue)
    let name = decoder.decodeObject(of: NSString.self, forKey: CodingKeys.name.rawValue)
    let linkURL = decoder.decodeObject(of: NSURL.self, forKey: CodingKeys.linkURL.rawValue)
    let refreshDate = decoder.decodeObject(of: NSDate.self, forKey: CodingKeys.refreshDate.rawValue)
    let imageURL = decoder.decodeObject(of: NSURL.self, forKey: CodingKeys.imageURL.rawValue)
    let email = decoder.decodeObject(of: NSString.self, forKey: CodingKeys.email.rawValue)
    let friendIDs = decoder.decodeObject(
      of: [NSArray.self, NSString.self],
      forKey: CodingKeys.friendIDs.rawValue
    ) as? [UserIdentifier]
    let isLimited = decoder.decodeBool(forKey: CodingKeys.isLimited.rawValue)
    let birthday = decoder.decodeObject(of: NSDate.self, forKey: CodingKeys.birthday.rawValue)
    let ageRange = decoder.decodeObject(of: UserAgeRange.self, forKey: CodingKeys.ageRange.rawValue)
    let hometown = decoder.decodeObject(of: Location.self, forKey: CodingKeys.hometown.rawValue)
    let location = decoder.decodeObject(of: Location.self, forKey: CodingKeys.location.rawValue)
    let gender = decoder.decodeObject(of: NSString.self, forKey: CodingKeys.gender.rawValue)

    self.init(
      userID: identifier,
      firstName: firstName as String?,
      middleName: middleName as String?,
      lastName: lastName as String?,
      name: name as String?,
      linkURL: linkURL as URL?,
      refreshDate: refreshDate as Date?,
      imageURL: imageURL as URL?,
      email: email as String?,
      friendIDs: friendIDs,
      birthday: birthday as Date?,
      ageRange: ageRange,
      hometown: hometown,
      location: location,
      gender: gender as String?,
      isLimited: isLimited
    )
  }

  public func encode(with encoder: NSCoder) {
    encoder.encode(userID, forKey: CodingKeys.userID.rawValue)
    encoder.encode(firstName, forKey: CodingKeys.firstName.rawValue)
    encoder.encode(middleName, forKey: CodingKeys.middleName.rawValue)
    encoder.encode(lastName, forKey: CodingKeys.lastName.rawValue)
    encoder.encode(name, forKey: CodingKeys.name.rawValue)
    encoder.encode(linkURL, forKey: CodingKeys.linkURL.rawValue)
    encoder.encode(refreshDate, forKey: CodingKeys.refreshDate.rawValue)
    encoder.encode(imageURL, forKey: CodingKeys.imageURL.rawValue)
    encoder.encode(email, forKey: CodingKeys.email.rawValue)
    encoder.encode(friendIDs, forKey: CodingKeys.friendIDs.rawValue)
    encoder.encode(isLimited, forKey: CodingKeys.isLimited.rawValue)
    encoder.encode(birthday, forKey: CodingKeys.birthday.rawValue)
    encoder.encode(ageRange, forKey: CodingKeys.ageRange.rawValue)
    encoder.encode(hometown, forKey: CodingKeys.hometown.rawValue)
    encoder.encode(location, forKey: CodingKeys.location.rawValue)
    encoder.encode(gender, forKey: CodingKeys.gender.rawValue)
  }

  enum CodingKeys: String, CodingKey {
    case userID
    case firstName
    case middleName
    case lastName
    case name
    case linkURL
    case refreshDate
    case imageURL
    case email
    case friendIDs
    case isLimited
    case birthday
    case ageRange
    case hometown
    case location
    case gender
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation

/**
 Represents an immutable Facebook profile.

 This class provides a global current profile instance to more easily
 add social context to your application. When the profile changes, a notification is
 posted so that you can update relevant parts of your UI. It is persisted to `UserDefaults.standard`.

 Typically, you will want to set `enableUpdatesOnAccessTokenChange` to `true` so that
 it automatically observes changes to `AccessToken.current`.

 You can use this class to build your own `ProfilePictureView` or in place of typical requests to the `/me` endpoint.
 */
@objcMembers
@objc(FBSDKProfile)
public final class Profile: NSObject {

  /// The user identifier.
  public let userID: UserIdentifier

  /// The user's first name.
  public let firstName: String?

  /// The user's middle name.
  public let middleName: String?

  /// The user's last name.
  public let lastName: String?

  /// The user's complete name.
  public let name: String?

  /**
   A URL to the user's profile.

   - Important: This field will only be populated if your user has granted your application the `user_link` permission.

   Consider using `AppLinkResolver` to resolve this URL to an app link in order to link directly to
   the user's profile in the Facebook app.
   */
  public let linkURL: URL?

  /// The last time the profile data was fetched.
  public let refreshDate: Date

  /// A URL to use for fetching the user's profile image.
  public let imageURL: URL?

  /**
   The user's email address.

   - Important: This field will only be populated if your user has granted your application the `email` permission.
   */
  public let email: String?

  /**
   A list of identifiers of the user's friends.

   - Important: This field will only be populated if your user has granted your application
     the `user_friends` permission.
   */
  public let friendIDs: [UserIdentifier]?

  /**
   The user's birthday.

   - Important: This field will only be populated if your user has granted your application
   the `user_birthday` permission.
   */
  public let birthday: Date?

  /**
   The user's age range.

   - Important: This field will only be populated if your user has granted your application
   the `user_age_range` permission.
   */
  public let ageRange: UserAgeRange?

  /**
   The user's hometown.

   - Important: This field will only be populated if your user has granted your application
   the `user_hometown` permission.
   */
  public let hometown: Location?

  /**
   The user's location.

   - Important: This field will only be populated if your user has granted your application
   the `user_location` permission.
   */
  public let location: Location?

  /**
   The user's gender.

   - Important: This field will only be populated if your user has granted your application
   the `user_gender` permission.
   */
  public let gender: String?

  let isLimited: Bool

  // swiftlint:disable:next identifier_name
  static var _current: Profile?

  /**
   Indicates whether this type will automatically observe access token changes
   (via `AccessTokenDidChange` notifications).

   If observing changes, this class will issue a Graph request for public profile data when the current token's user
   identifier differs from the current profile. You can observe profile changes via `ProfileDidChange` notifications
   to handle an updated profile.

   - Note: If the current access token is cleared, the current profile instance remains available. It's also possible
   for `current` to return `nil` until the data is fetched.
   */
  public static var isUpdatedWithAccessTokenChange = false {
    didSet {
      if isUpdatedWithAccessTokenChange {
        Self.notificationCenter?.fb_addObserver(
          self,
          selector: #selector(observeAccessTokenChange(_:)),
          name: .AccessTokenDidChange,
          object: nil
        )
      } else {
        Self.notificationCenter?.fb_removeObserver(self)
      }
    }
  }

  /**
   Creates a new profile.

   - Parameters:
     - userID: The user's identifier.
     - firstName: The user's first name. Defaults to `nil`.
     - middleName: The user's middle name. Defaults to `nil`.
     - lastName: The user's last name. Defaults to `nil`.
     - name: The user's complete name. Defaults to `nil`.
     - linkURL: The link for the profile. Defaults to `nil`.
     - refreshDate: The date the profile was fetched. Defaults to the time of instantiation.
   */
  @objc(initWithUserID:firstName:middleName:lastName:name:linkURL:refreshDate:)
  public convenience init(
    userID: UserIdentifier,
    firstName: String?,
    middleName: String?,
    lastName: String?,
    name: String?,
    linkURL: URL?,
    refreshDate: Date?
  ) {
    self.init(
      userID: userID,
      firstName: firstName,
      middleName: middleName,
      lastName: lastName,
      name: name,
      linkURL: linkURL,
      refreshDate: refreshDate,
      imageURL: nil,
      email: nil,
      friendIDs: nil,
      birthday: nil,
      ageRange: nil,
      hometown: nil,
      location: nil,
      gender: nil,
      isLimited: false
    )
  }

  // swiftlint:disable:next swiftlint_disable_without_this_or_next
  // swiftlint:disable line_length
  // swiftformat:disable blankLinesBetweenScopes

  /**
   Creates a new profile.

   - Parameters:
     - userID: The user's identifier. Defaults to `nil`.
     - firstName: The user's first name. Defaults to `nil`.
     - middleName: The user's middle name. Defaults to `nil`.
     - lastName: The user's last name. Defaults to `nil`.
     - name: The user's complete name. Defaults to `nil`.
     - linkURL: The link for this profile. Defaults to `nil`.
     - refreshDate: The date this profile was fetched. Defaults to the time of instantiation.
     - imageURL: A URL to use for fetching a user's profile image.
     - email: The user's email address. Defaults to `nil`.
     - friendIDs: A list of identifiers for the user's friends. Defaults to `nil`.
     - birthday: The user's birthday. Defaults to `nil`.
     - ageRange: The user's age range. Defaults to `nil`.
     - hometown: The user's hometown. Defaults to `nil`.
     - location: The user's location. Defaults to `nil`.
     - gender: The user's gender. Defaults to `nil`.
   */
  @objc(initWithUserID:firstName:middleName:lastName:name:linkURL:refreshDate:imageURL:email:friendIDs:birthday:ageRange:hometown:location:gender:)
  public convenience init(
    userID: UserIdentifier,
    firstName: String? = nil,
    middleName: String? = nil,
    lastName: String? = nil,
    name: String? = nil,
    linkURL: URL? = nil,
    refreshDate: Date? = Date(),
    imageURL: URL? = nil,
    email: String? = nil,
    friendIDs: [UserIdentifier]? = nil,
    birthday: Date? = nil,
    ageRange: UserAgeRange? = nil,
    hometown: Location? = nil,
    location: Location? = nil,
    gender: String? = nil
  ) {
    self.init(
      userID: userID,
      firstName: firstName,
      middleName: middleName,
      lastName: lastName,
      name: name,
      linkURL: linkURL,
      refreshDate: refreshDate,
      imageURL: imageURL,
      email: email,
      friendIDs: friendIDs,
      birthday: birthday,
      ageRange: ageRange,
      hometown: hometown,
      location: location,
      gender: gender,
      isLimited: false
    )
  }

  /**
   Creates a new profile.

   - Parameters:
     - userID: The user's identifier. Defaults to `nil`.
     - firstName: The user's first name. Defaults to `nil`.
     - middleName: The user's middle name. Defaults to `nil`.
     - lastName: The user's last name. Defaults to `nil`.
     - name: The user's complete name. Defaults to `nil`.
     - linkURL: The link for the profile. Defaults to `nil`.
     - refreshDate: The date the profile was fetched. Defaults to the time of instantiation.
     - imageURL: A URL to use for fetching the user's profile image Defaults to `nil`.
     - email: The user's email address. Defaults to `nil`.
     - friendIDs: A list of identifiers for the user's friends. Defaults to `nil`.
     - birthday: The user's birthday. Defaults to `nil`.
     - ageRange: The user's age range. Defaults to `nil`.
     - hometown: The user's hometown. Defaults to `nil`.
     - location: The user's location. Defaults to `nil`.
     - gender: The user's gender. Defaults to `nil`.
     - isLimited: Indicates whether the information provided is incomplete in some way.
       When `true`, `loadCurrentProfile(completion:):` will assume the profile is incomplete and disregard
       any cached profile. Defaults to `false`.
   */
  @objc(initWithUserID:firstName:middleName:lastName:name:linkURL:refreshDate:imageURL:email:friendIDs:birthday:ageRange:hometown:location:gender:isLimited:)
  public init(
    userID: UserIdentifier,
    firstName: String?,
    middleName: String?,
    lastName: String?,
    name: String?,
    linkURL: URL?,
    refreshDate: Date?,
    imageURL: URL?,
    email: String?,
    friendIDs: [UserIdentifier]?,
    birthday: Date?,
    ageRange: UserAgeRange?,
    hometown: Location?,
    location: Location?,
    gender: String?,
    isLimited: Bool
  ) {
    self.userID = userID
    self.firstName = firstName
    self.middleName = middleName
    self.lastName = lastName
    self.name = name
    self.linkURL = linkURL
    self.refreshDate = refreshDate ?? Date()
    self.imageURL = imageURL
    self.email = email
    self.friendIDs = friendIDs
    self.birthday = birthday
    self.ageRange = ageRange
    self.hometown = hometown
    self.location = location
    self.gender = gender
    self.isLimited = isLimited

    super.init()
  }

  /**
   Indicates whether this type will automatically observe access token changes
   (via `AccessTokenDidChange` notifications).

   If observing changes, this class will issue a Graph request for public profile data when the current token's user
   identifier differs from the current profile. You can observe profile changes via `ProfileDidChange` notifications
   to handle an updated profile.

   - Note: If the current access token is cleared, the current profile instance remains available. It's also possible
   for `current` to return `nil` until the data is fetched.
   */
  @available(*, deprecated, message: "This method is deprecated and will be removed in the next major release. Use `isUpdatedWithAccessTokenChange` instead.")
  @objc(enableUpdatesOnAccessTokenChange:)
  public static func enableUpdatesOnAccessTokenChange(_ enabled: Bool) {
    isUpdatedWithAccessTokenChange = enabled
  }

  // swiftlint:enable line_length
  // swiftformat:enable blankLinesBetweenScopes

  @objc private static func observeAccessTokenChange(_ notification: Notification) {
    loadProfile(
      accessToken: notification.userInfo?[AccessTokenChangeNewKey] as? AccessToken,
      completion: nil
    )
  }
}

extension Profile: DependentAsType {
  struct TypeDependencies {
    var accessTokenProvider: _AccessTokenProviding.Type
    var dataStore: DataPersisting
    var graphRequestFactory: GraphRequestFactoryProtocol
    var notificationCenter: _NotificationPosting & NotificationDelivering
    var settings: SettingsProtocol
    var urlHoster: URLHosting
  }

  static var configuredDependencies: TypeDependencies?

  static var defaultDependencies: TypeDependencies?
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

/**
 Internal type exposed to facilitate transition to Swift.
 API Subject to change or removal without warning. Do not use.

 @warning INTERNAL - DO NOT USE
 */
@objc(FBSDKProfileProviding)
public protocol ProfileProviding {
  @objc(currentProfile)
  static var current: Profile? { get set }

  static func fetchCachedProfile() -> Profile?
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

/**
 Internal typealias exposed to facilitate transition to Swift.
 API Subject to change or removal without warning. Do not use.

 @warning INTERNAL - DO NOT USE
 */
public typealias LoginTooltipBlock = (FBSDKLoginTooltip?, Error?) -> Void

/**
 Internal Type exposed to facilitate transition to Swift.
 API Subject to change or removal without warning. Do not use.

 @warning INTERNAL - DO NOT USE
 */
public enum LoginTooltipError: Error {
  case missingTooltipText
}

/**
 Internal Type exposed to facilitate transition to Swift.
 API Subject to change or removal without warning. Do not use.

 @warning INTERNAL - DO NOT USE
 */
public final class ServerConfigurationProvider: NSObject {

  public var loggingToken: String? {
    _ServerConfigurationManager.shared.cachedServerConfiguration().loggingToken
  }

  public func shouldUseSafariViewController(forDialogName dialogName: String) -> Bool {
    _ServerConfigurationManager.shared.cachedServerConfiguration().useSafariViewController(forDialogName: dialogName)
  }

  public func loadServerConfiguration(completion: LoginTooltipBlock?) {
    _ServerConfigurationManager.shared.loadServerConfiguration { potentialConfiguration, potentialError in
      DispatchQueue.main.async {
        guard let tooltipCompletion = completion else { return }

        guard let configuration = potentialConfiguration,
              potentialError == nil
        else {
          return tooltipCompletion(nil, potentialError)
        }

        guard let text = configuration.loginTooltipText else {
          return tooltipCompletion(nil, LoginTooltipError.missingTooltipText)
        }

        let tooltip = FBSDKLoginTooltip(
          text: text,
          enabled: configuration.isLoginTooltipEnabled
        )

        tooltipCompletion(tooltip, nil)
      }
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

extension Settings {
  /**
   Internal method exposed to facilitate transition to Swift.
   API Subject to change or removal without warning. Do not use.

   @warning INTERNAL - DO NOT USE
   */
  public func recordInstall() {
    guard
      let dataStore = try? getDependencies().dataStore,
      dataStore.fb_object(forKey: PersistenceKey.installTimestamp.rawValue) == nil
    else { return }

    dataStore.fb_setObject(Date(), forKey: PersistenceKey.installTimestamp.rawValue)
  }

  func recordSetAdvertiserTrackingEnabled() {
    // swiftformat:disable:next redundantSelf
    self.dataStore?.fb_setObject(Date(), forKey: PersistenceKey.setAdvertiserTrackingEnabledTimestamp.rawValue)
  }

  /**
   Internal method exposed to facilitate transition to Swift.
   API Subject to change or removal without warning. Do not use.

   @warning INTERNAL - DO NOT USE
   */
  public func logWarnings() {
    // Log warnings for App Event Flags
    // swiftformat:disable:next redundantSelf
    if self.infoDictionaryProvider?
      .fb_object(forInfoDictionaryKey: PersistenceKey.isAutoLogAppEventsEnabled.rawValue) == nil {
      print(AppEventFlagWarningMessages.isAutoLogAppEventsEnabledNotSet)
    }

    // swiftformat:disable:next redundantSelf
    if self.infoDictionaryProvider?
      .fb_object(forInfoDictionaryKey: PersistenceKey.isAdvertiserIDCollectionEnabled.rawValue) == nil {
      print(AppEventFlagWarningMessages.isAdvertiserIDCollectionEnabledNotSet)
    }

    if !(_isAdvertiserIDCollectionEnabled ?? false) {
      print(AppEventFlagWarningMessages.isAdvertiserIDCollectionEnabledFalse)
    }
  }

  /**
   Internal method exposed to facilitate transition to Swift.
   API Subject to change or removal without warning. Do not use.

   @warning INTERNAL - DO NOT USE
   */
  public func logIfSDKSettingsChanged() {
    guard let dependencies = try? getDependencies() else { return }

    var bitmask = 0

    // Starting at 1 to maintain the meaning of the bits since the `autoInit` flag was removed.
    var bit = 1

    bitmask |= (isAutoLogAppEventsEnabled ? 1 : 0) << bit
    bit += 1

    bitmask |= (isAdvertiserIDCollectionEnabled ? 1 : 0) << bit
    bit += 1

    let previousBitmask = dependencies.dataStore.fb_integer(forKey: PersistenceKey.bitmask.rawValue)
    guard bitmask != previousBitmask else { return }

    dependencies.dataStore.fb_setInteger(bitmask, forKey: PersistenceKey.bitmask.rawValue)

    var initialBitmask = 0
    var usageBitmask = 0
    let bits: [(key: PersistenceKey, defaultValue: Bool)] = [
      (key: .isAutoLogAppEventsEnabled, defaultValue: true),
      (key: .isAdvertiserIDCollectionEnabled, defaultValue: true),
    ]

    bit = 0
    bits.forEach { key, defaultValue in
      let potentialFlag = (dependencies.infoDictionaryProvider.fb_object(forInfoDictionaryKey: key.rawValue) as? Bool)

      initialBitmask |= ((potentialFlag ?? defaultValue) ? 1 : 0) << bit
      usageBitmask |= ((potentialFlag != nil) ? 1 : 0) << bit

      bit += 1
    }

    dependencies.eventLogger.logInternalEvent(
      .sdkSettingsChanged,
      parameters: [
        .sdkSettingsUsageBitmask: usageBitmask,
        .sdkSettingsInitialBitmask: initialBitmask,
        .sdkSettingsPreviousBitmask: previousBitmask,
        .sdkSettingsCurrentBitmask: bitmask,
      ],
      isImplicitlyLogged: true
    )
  }

  private enum AppEventFlagWarningMessages {
    static let isAutoLogAppEventsEnabledNotSet = """
      <Warning>: Please set a value for FacebookAutoLogAppEventsEnabled. Set the flag to TRUE if you want \
      to collect app install, app launch and in-app purchase events automatically. To request user consent \
      before collecting data, set the flag value to FALSE, then change to TRUE once user consent is received. \
      Learn more: https://developers.facebook.com/docs/app-events/getting-started-app-events-ios#disable-auto-events.
      """
    static let isAdvertiserIDCollectionEnabledNotSet = """
      <Warning>: You haven't set a value for FacebookAdvertiserIDCollectionEnabled. Set the flag to TRUE if \
      you want to collect Advertiser ID for better advertising and analytics results.
      """
    static let isAdvertiserIDCollectionEnabledFalse = """
      <Warning>: The value for FacebookAdvertiserIDCollectionEnabled is currently set to FALSE so you're sending app \
      events without collecting Advertiser ID. This can affect the quality of your advertising and analytics results.
      """
  }
}

// swiftformat:disable:next extensionAccessControl
fileprivate extension AppEvents.ParameterName {
  static let sdkSettingsUsageBitmask = Self("usage")
  static let sdkSettingsInitialBitmask = Self("initial")
  static let sdkSettingsPreviousBitmask = Self("previous")
  static let sdkSettingsCurrentBitmask = Self("current")
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation

extension Settings {
  struct PersistedStringProperty {
    let persistenceKey: Settings.PersistenceKey
    let backingKeyPath: ReferenceWritableKeyPath<Settings, String?>

    static let appURLSchemeSuffix = Self(
      persistenceKey: .urlSchemeSuffix,
      backingKeyPath: \._appURLSchemeSuffix
    )

    static let clientToken = Self(
      persistenceKey: .clientToken,
      backingKeyPath: \._clientToken
    )

    static let displayName = Self(
      persistenceKey: .displayName,
      backingKeyPath: \._displayName
    )

    static let facebookDomainPart = Self(
      persistenceKey: .domainPart,
      backingKeyPath: \._facebookDomainPart
    )
  }

  func getPersistedStringProperty(_ property: PersistedStringProperty) -> String? {
    if let value = self[keyPath: property.backingKeyPath] {
      return value
    }

    // swiftformat:disable:next redundantSelf
    guard let infoDictionaryProvider = self.infoDictionaryProvider else { return nil }

    let value = infoDictionaryProvider.fb_object(forInfoDictionaryKey: property.persistenceKey.rawValue) as? String
    self[keyPath: property.backingKeyPath] = value
    return value
  }

  func setPersistedStringProperty(_ property: PersistedStringProperty, to value: String?) {
    validateConfiguration()
    self[keyPath: property.backingKeyPath] = value
    logIfSDKSettingsChanged()
  }
}

extension Settings {
  struct PersistedBooleanValue {
    let persistenceKey: Settings.PersistenceKey
    let backingKeyPath: ReferenceWritableKeyPath<Settings, Bool?> // swiftlint:disable:this discouraged_optional_boolean
    let defaultValue: Bool

    static let isAutoLogAppEventsEnabled = Self(
      persistenceKey: .isAutoLogAppEventsEnabled,
      backingKeyPath: \._isAutoLogAppEventsEnabled,
      defaultValue: true
    )

    static let isAdvertiserIDCollectionEnabled = Self(
      persistenceKey: .isAdvertiserIDCollectionEnabled,
      backingKeyPath: \._isAdvertiserIDCollectionEnabled,
      defaultValue: true
    )

    static let isCodelessDebugLogEnabled = Self(
      persistenceKey: .isCodelessDebugLogEnabled,
      backingKeyPath: \._isCodelessDebugLogEnabled,
      defaultValue: false
    )
  }

  func getPersistedBooleanProperty(_ property: PersistedBooleanValue) -> Bool {
    if let value = self[keyPath: property.backingKeyPath] {
      return value
    }

    guard let dependencies = try? getDependencies() else { return property.defaultValue }

    let numberValue = dependencies.dataStore.fb_object(forKey: property.persistenceKey.rawValue) as? NSNumber
      // swiftlint:disable:next line_length
      ?? dependencies.infoDictionaryProvider.fb_object(forInfoDictionaryKey: property.persistenceKey.rawValue) as? NSNumber

    let value = numberValue?.boolValue ?? property.defaultValue
    self[keyPath: property.backingKeyPath] = value
    return value
  }

  func setPersistedBooleanProperty(_ property: PersistedBooleanValue, to value: Bool) {
    validateConfiguration()
    // swiftformat:disable:next redundantSelf
    self.dataStore?.fb_setObject(value, forKey: property.persistenceKey.rawValue)
    logIfSDKSettingsChanged()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

extension Settings {
  enum PersistenceKey: String {
    case urlSchemeSuffix = "FacebookUrlSchemeSuffix"
    case clientToken = "FacebookClientToken"
    case displayName = "FacebookDisplayName"
    case domainPart = "FacebookDomainPart"
    case isAutoLogAppEventsEnabled = "FacebookAutoLogAppEventsEnabled"
    case isAdvertiserIDCollectionEnabled = "FacebookAdvertiserIDCollectionEnabled"
    case isCodelessDebugLogEnabled = "FacebookCodelessDebugLogEnabled"
    case loggingBehaviors = "FacebookLoggingBehavior"
    case appID = "FacebookAppID"
    case jpegCompressionQuality = "FacebookJpegCompressionQuality"
    case isSKAdNetworkReportEnabled = "FacebookSKAdNetworkReportEnabled"
    case advertisingTrackingStatus = "com.facebook.sdk:FBSDKSettingsAdvertisingTrackingStatus"
    case limitEventAndDataUsage = "com.facebook.sdk:FBSDKSettingsLimitEventAndDataUsage"
    case useCachedValuesForExpensiveMetadata = "com.facebook.sdk:FBSDKSettingsUseCachedValuesForExpensiveMetadata"
    case useTokenOptimizations = "com.facebook.sdk.FBSDKSettingsUseTokenOptimizations"
    case dataProcessingOptions = "com.facebook.sdk:FBSDKSettingsDataProcessingOptions"
    case bitmask = "com.facebook.sdk:FBSDKSettingsBitmask"
    case installTimestamp = "com.facebook.sdk:FBSDKSettingsInstallTimestamp"
    case setAdvertiserTrackingEnabledTimestamp = "com.facebook.sdk:FBSDKSettingsSetAdvertiserTrackingEnabledTimestamp"
  }
}