/*
 * 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 AdSupport
import Foundation

@objcMembers
@objc(FBSDKSettings)
public final class Settings: NSObject, SettingsProtocol, SettingsLogging, _ClientTokenProviding {

  var configuredDependencies: ObjectDependencies?
  var defaultDependencies: ObjectDependencies?

  /// The shared settings instance. Prefer this and the exposed instance methods over the type properties and methods.
  @objc(sharedSettings)
  public static let shared = Settings()

  /// The Facebook SDK version in use.
  public var sdkVersion: String { FBSDK_VERSION_STRING }

  /// The default Graph API version.
  public var defaultGraphAPIVersion: String { FBSDK_DEFAULT_GRAPH_API_VERSION }

  /**
   The quality of JPEG images sent to Facebook from the SDK expressed as a value from 0.0 to 1.0.

   The default value is 0.9.
   */
  @objc(JPEGCompressionQuality)
  public var jpegCompressionQuality: CGFloat {
    get { _jpegCompressionQuality }
    set {
      validateConfiguration()
      _jpegCompressionQuality = clampJPEGCompressionQuality(newValue)
      logIfSDKSettingsChanged()
    }
  }

  private static let defaultJPEGCompressionQuality: CGFloat = 0.9

  private lazy var _jpegCompressionQuality = clampJPEGCompressionQuality(
    self.infoDictionaryProvider?
      .fb_object(forInfoDictionaryKey: PersistenceKey.jpegCompressionQuality.rawValue) as? CGFloat
      ?? Self.defaultJPEGCompressionQuality
  )

  private func clampJPEGCompressionQuality(_ quality: CGFloat) -> CGFloat {
    quality.fb_clamped(to: 0.0 ... 1.0)
  }

  // swiftlint:disable:next swiftlint_disable_without_this_or_next
  // swiftlint:disable let_var_whitespace

  /**
   Controls the automatic logging of basic app events such as `activateApp` and `deactivateApp`.

   The default value is `true`.
   */
  @available(
    *,
    deprecated,
    message: """
      This property is deprecated and will be removed in the next major release. \
      Use `isAutoLogAppEventsEnabled` instead.
      """
  )
  public var autoLogAppEventsEnabled: Bool {
    get { isAutoLogAppEventsEnabled }
    set { isAutoLogAppEventsEnabled = newValue }
  }

  /**
   Controls the automatic logging of basic app events such as `activateApp` and `deactivateApp`.

   The default value is `true`.
   */
  public var isAutoLogAppEventsEnabled: Bool {
    get { getPersistedBooleanProperty(.isAutoLogAppEventsEnabled) }
    set { setPersistedBooleanProperty(.isAutoLogAppEventsEnabled, to: newValue) }
  }

  // swiftlint:disable:next identifier_name discouraged_optional_boolean
  var _isAutoLogAppEventsEnabled: Bool?

  /**
   Controls the `fb_codeless_debug` logging event.

   The default value is `false`.
   */
  @available(
    *,
    deprecated,
    message: """
      This property is deprecated and will be removed in the next major release. \
      Use `isCodelessDebugLogEnabled` instead.
      """
  )
  public var codelessDebugLogEnabled: Bool {
    get { isCodelessDebugLogEnabled }
    set { isCodelessDebugLogEnabled = newValue }
  }

  /**
   Controls the `fb_codeless_debug` logging event.

   The default value is `false`.
   */
  public var isCodelessDebugLogEnabled: Bool {
    get { getPersistedBooleanProperty(.isCodelessDebugLogEnabled) }
    set { setPersistedBooleanProperty(.isCodelessDebugLogEnabled, to: newValue) }
  }

  // swiftlint:disable:next identifier_name discouraged_optional_boolean
  var _isCodelessDebugLogEnabled: Bool?

  /**
   Controls the access to IDFA.

   The default value is `true`.
   */
  @available(
    *,
    deprecated,
    message: """
      This property is deprecated and will be removed in the next major release. \
      Use `isAdvertiserIDCollectionEnabled` instead.
      """
  )
  public var advertiserIDCollectionEnabled: Bool {
    get { isAdvertiserIDCollectionEnabled }
    set { isAdvertiserIDCollectionEnabled = newValue }
  }

  /**
   Controls the access to IDFA.

   The default value is `true`.
   */
  public var isAdvertiserIDCollectionEnabled: Bool {
    get { getPersistedBooleanProperty(.isAdvertiserIDCollectionEnabled) }
    set { setPersistedBooleanProperty(.isAdvertiserIDCollectionEnabled, to: newValue) }
  }

  // swiftlint:disable:next identifier_name discouraged_optional_boolean
  var _isAdvertiserIDCollectionEnabled: Bool?

  /**
   Controls the SKAdNetwork report.

   The default value is `true`.
   */
  @available(
    *,
    deprecated,
    message: """
      This property is deprecated and will be removed in the next major release. \
      Use `isSKAdNetworkReportEnabled` instead.
      """
  )
  public var skAdNetworkReportEnabled: Bool {
    get { isSKAdNetworkReportEnabled }
    set { isSKAdNetworkReportEnabled = newValue }
  }

  /**
   Controls the SKAdNetwork report.

   The default value is `true`.
   */
  public var isSKAdNetworkReportEnabled: Bool {
    get { _isSKAdNetworkReportEnabled }
    set {
      validateConfiguration()
      _isSKAdNetworkReportEnabled = newValue
      // swiftformat:disable:this redundantSelf
      self.dataStore?.fb_setObject(newValue, forKey: PersistenceKey.isSKAdNetworkReportEnabled.rawValue)
      logIfSDKSettingsChanged()
    }
  }

  private lazy var _isSKAdNetworkReportEnabled: Bool = {
    let key = PersistenceKey.isSKAdNetworkReportEnabled.rawValue
    return self.dataStore?.fb_object(forKey: key) as? Bool
      ?? self.infoDictionaryProvider?.fb_object(forInfoDictionaryKey: key) as? Bool
      ?? true
  }()

  /**
   Whether data such as that generated through `AppEvents` and sent to Facebook
   should be restricted from being used for purposes other than analytics and conversions.

   The default value is `false`. This value is stored on the device and persists across app launches.
   */
  public var isEventDataUsageLimited: Bool {
    get { _isEventDataUsageLimited }
    set {
      _isEventDataUsageLimited = newValue
      // swiftformat:disable:this redundantSelf
      self.dataStore?.fb_setObject(newValue, forKey: PersistenceKey.limitEventAndDataUsage.rawValue)
    }
  }

  private lazy var _isEventDataUsageLimited = self.dataStore?
    .fb_object(forKey: PersistenceKey.limitEventAndDataUsage.rawValue) as? Bool
    ?? false

  /**
   Whether in-memory cached values should be used for expensive metadata fields, such as
   carrier and advertiser ID, that are fetched on many `applicationDidBecomeActive` notifications.

   The default value is `false`. This value is stored on the device and persists across app launches.
   */
  public var shouldUseCachedValuesForExpensiveMetadata: Bool {
    get { _shouldUseCachedValuesForExpensiveMetadata }
    set {
      _shouldUseCachedValuesForExpensiveMetadata = newValue
      self.dataStore?.fb_setObject(newValue, forKey: PersistenceKey.useCachedValuesForExpensiveMetadata.rawValue)
    }
  }

  private lazy var _shouldUseCachedValuesForExpensiveMetadata = self.dataStore?
    .fb_object(forKey: PersistenceKey.useCachedValuesForExpensiveMetadata.rawValue) as? Bool
    ?? false

  /// Controls error recovery for all `GraphRequest` instances created after the value is changed.
  public var isGraphErrorRecoveryEnabled = true

  /**
   The Facebook App ID used by the SDK.

   The default value will be read from the application's plist (FacebookAppID).
   */
  public var appID: String? {
    get { _appID }
    set {
      validateConfiguration()
      _appID = newValue
      logIfSDKSettingsChanged()
    }
  }

  private lazy var _appID = self.infoDictionaryProvider?
    .fb_object(forInfoDictionaryKey: PersistenceKey.appID.rawValue) as? String

  /**
   The default URL scheme suffix used for sessions.

   The default value will be read from the application's plist (FacebookUrlSchemeSuffix).
   */
  public var appURLSchemeSuffix: String? {
    get { getPersistedStringProperty(.appURLSchemeSuffix) }
    set { setPersistedStringProperty(.appURLSchemeSuffix, to: newValue) }
  }

  // swiftlint:disable:next identifier_name
  var _appURLSchemeSuffix: String?

  /**
   The client token needed for certain anonymous API calls (i.e., those made without a user-based access token).

   An app's client token can be found by navigating to https://developers.facebook.com/apps/YOUR-APP-ID
   (replacing "YOUR-APP-ID" with your actual app ID), choosing "Settings->Advanced" and scrolling to the "Security".

   The default value will be read from the application's plist (FacebookClientToken).
   */
  public var clientToken: String? {
    get { getPersistedStringProperty(.clientToken) }
    set { setPersistedStringProperty(.clientToken, to: newValue) }
  }

  // swiftlint:disable:next identifier_name
  var _clientToken: String?

  /**
   The Facebook Display Name used by the SDK.

   This should match the Display Name that has been set for the app with the corresponding Facebook App ID
   in the Facebook App Dashboard.

   The default value will be read from the application's plist (FacebookDisplayName).
   */
  public var displayName: String? {
    get { getPersistedStringProperty(.displayName) }
    set { setPersistedStringProperty(.displayName, to: newValue) }
  }

  // swiftlint:disable:next identifier_name
  var _displayName: String?
  /**
   The Facebook domain part. This can be used to change the Facebook domain
   (e.g. "beta") so that requests will be sent to `graph.beta.facebook.com`.

   The default value will be read from the application's plist (FacebookDomainPart).
   */
  public var facebookDomainPart: String? {
    get { getPersistedStringProperty(.facebookDomainPart) }
    set { setPersistedStringProperty(.facebookDomainPart, to: newValue) }
  }

  // swiftlint:disable:next identifier_name
  var _facebookDomainPart: String?

  /**
   Overrides the default Graph API version to use with `GraphRequest` instances.

   The string should be of the form `"v2.7"`.

   The default value is `defaultGraphAPIVersion`.
   */
  public lazy var graphAPIVersion = defaultGraphAPIVersion

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

   @warning INTERNAL - DO NOT USE
   */
  public var userAgentSuffix: String?

  /**
   Controls the advertiser tracking status of the data sent to Facebook.
   */
  @available(
    *,
    deprecated,
    message: """
      This property is deprecated and will be removed in the next major release. \
      Use `isAdvertiserTrackingEnabled` instead.
      """
  )
  public var advertiserTrackingEnabled: Bool {
    get { isAdvertiserTrackingEnabled }
    set { isAdvertiserTrackingEnabled = newValue }
  }

  /**
   Controls the advertiser tracking status of the data sent to Facebook.
   */
  public var isAdvertiserTrackingEnabled: Bool {
    get { advertisingTrackingStatus == .allowed }
    set(isNewlyAllowed) {
      if #available(iOS 14.0, *) {
        advertisingTrackingStatus = isNewlyAllowed ? .allowed : .disallowed
        recordSetAdvertiserTrackingEnabled()
      }
    }
  }

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

   @warning INTERNAL - DO NOT USE
   */
  public var advertisingTrackingStatus: AdvertisingTrackingStatus {
    get {
      if #available(iOS 14, *) {
        return _advertisingTrackingStatus
      } else {
        return ASIdentifierManager.shared().isAdvertisingTrackingEnabled ? .allowed : .disallowed
      }
    }
    set {
      _advertisingTrackingStatus = newValue
      self.dataStore?.fb_setObject(newValue.rawValue, forKey: PersistenceKey.advertisingTrackingStatus.rawValue)
    }
  }

  private lazy var _advertisingTrackingStatus: AdvertisingTrackingStatus = {
    guard let dependencies = try? getDependencies() else { return .unspecified }

    if let persisted = dependencies.dataStore
      .fb_object(forKey: PersistenceKey.advertisingTrackingStatus.rawValue) as? NSNumber {
      return AdvertisingTrackingStatus(rawValue: persisted.uintValue) ?? .unspecified
    } else {
      return dependencies.appEventsConfigurationProvider.cachedAppEventsConfiguration.defaultATEStatus
    }
  }()

  private static let isDataProcessingRestrictedOption = "ldu"

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

   @warning INTERNAL - DO NOT USE
   */
  public var isDataProcessingRestricted: Bool {
    guard let options = persistableDataProcessingOptions?[DataProcessingOptionKey.options.rawValue] as? [String] else {
      return false
    }

    return options.contains {
      $0.caseInsensitiveCompare(Self.isDataProcessingRestrictedOption) == .orderedSame
    }
  }

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

   @warning INTERNAL - DO NOT USE
   */
  public private(set) lazy var persistableDataProcessingOptions: [DataProcessingOptionKey.RawValue: Any]? = {
    guard let data = self.dataStore?.fb_object(forKey: PersistenceKey.dataProcessingOptions.rawValue) as? Data else {
      return nil
    }

    let classes: [AnyClass] = [
      NSString.self,
      NSNumber.self,
      NSArray.self,
      NSDictionary.self,
      NSSet.self,
    ]

    return try? NSKeyedUnarchiver.unarchivedObject(ofClasses: classes, from: data)
      as? [DataProcessingOptionKey.RawValue: Any]
  }()

  /**
   Set the data processing options.

   - Parameter options: The list of options.
   */
  public func setDataProcessingOptions(_ options: [String]?) {
    setDataProcessingOptions(options, country: 0, state: 0)
  }

  /**
   Sets the data processing options.

   - Parameters:
     - options The list of the options.
     - country The code for the country.
     - state The code for the state.
   */
  public func setDataProcessingOptions(_ options: [String]?, country: Int32, state: Int32) {
    let values: [DataProcessingOptionKey.RawValue: Any] = [
      DataProcessingOptionKey.options.rawValue: options ?? [],
      DataProcessingOptionKey.country.rawValue: country,
      DataProcessingOptionKey.state.rawValue: state,
    ]

    persistableDataProcessingOptions = values
    if let data = try? NSKeyedArchiver.archivedData(withRootObject: values, requiringSecureCoding: false) {
      self.dataStore?.fb_setObject(data, forKey: PersistenceKey.dataProcessingOptions.rawValue)
    }
  }

  /**
   The current Facebook SDK logging behavior. This should consist of strings
   defined as constants with `LoggingBehavior` that indicate what information should be logged.

   Set to an empty set in order to disable all logging.

   You can also define this via an array in your app's plist with the key "FacebookLoggingBehavior"; or add/remove
   individual values via `enableLoggingBehavior(_:)` or `disableLoggingBehavior(_:)`

   The default value is `[.developerErrors]`.
   */
  public var loggingBehaviors: Set<LoggingBehavior> {
    get { _loggingBehaviors }
    set {
      _loggingBehaviors = newValue
      updateGraphAPIDebugBehavior()
    }
  }

  private lazy var _loggingBehaviors: Set<LoggingBehavior> = {
    if let persisted = self.infoDictionaryProvider?
      .fb_object(forInfoDictionaryKey: PersistenceKey.loggingBehaviors.rawValue) as? [LoggingBehavior] {
      return Set(persisted)
    } else {
      // Establish set of default enabled logging behaviors.  You can completely disable logging by
      // specifying an empty array for FacebookLoggingBehavior in your Info.plist.
      return [.developerErrors]
    }
  }()

  /**
   Enable a particular Facebook SDK logging behavior.

   - Parameter loggingBehavior: The logging behavior to enable. This should be a string constant defined
   as a `LoggingBehavior`.
   */
  public func enableLoggingBehavior(_ loggingBehavior: LoggingBehavior) {
    loggingBehaviors.insert(loggingBehavior)
  }

  /**
   Disable a particular Facebook SDK logging behavior.

   - Parameter loggingBehavior: The logging behavior to disable. This should be a string constant defined
   as a `LoggingBehavior`.
   */
  public func disableLoggingBehavior(_ loggingBehavior: LoggingBehavior) {
    loggingBehaviors.remove(loggingBehavior)
  }

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

   @warning INTERNAL - DO NOT USE
   */
  public var shouldUseTokenOptimizations: Bool {
    get { _shouldUseTokenOptimizations }
    set {
      _shouldUseTokenOptimizations = newValue
      self.dataStore?.fb_setObject(newValue, forKey: PersistenceKey.useTokenOptimizations.rawValue)
    }
  }

  private lazy var _shouldUseTokenOptimizations = self.dataStore?
    .fb_object(forKey: PersistenceKey.useTokenOptimizations.rawValue) as? Bool
    ?? true

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

   @warning INTERNAL - DO NOT USE
   */
  @available(
    *,
    deprecated,
    message: """
      This property is deprecated and will be removed in the next major release. \
      Use `isATETimeSufficientlyDelayed` instead.
      """
  )
  public var isSetATETimeExceedsInstallTime: Bool { isATETimeSufficientlyDelayed }

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

   @warning INTERNAL - DO NOT USE
   */
  public var isATETimeSufficientlyDelayed: Bool {
    guard
      let installTimestamp = installTimestamp,
      let ateTimestamp = advertiserTrackingEnabledTimestamp
    else { return false }

    let oneDayInSeconds: TimeInterval = 60.0 /* seconds */ * 60.0 /* minutes */ * 24.0 /* hours */
    return ateTimestamp.timeIntervalSince(installTimestamp) > oneDayInSeconds
  }

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

   @warning INTERNAL - DO NOT USE
   */
  public var installTimestamp: Date? {
    self.dataStore?.fb_object(forKey: PersistenceKey.installTimestamp.rawValue) as? Date
  }

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

   @warning INTERNAL - DO NOT USE
   */
  public var advertiserTrackingEnabledTimestamp: Date? {
    self.dataStore?.fb_object(forKey: PersistenceKey.setAdvertiserTrackingEnabledTimestamp.rawValue) as? Date
  }

  private func updateGraphAPIDebugBehavior() {
    // Enable warnings every time info is enabled
    if loggingBehaviors.contains(.graphAPIDebugInfo) {
      loggingBehaviors.insert(.graphAPIDebugWarning)
    }
  }

  private enum GraphAPIDebugParameterValue: String {
    case info
    case warning
  }

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

   @warning INTERNAL - DO NOT USE
   */
  @available(
    *,
    deprecated,
    message: """
      This property is deprecated and will be removed in the next major release. \
      Use `graphAPIDebugParameterValue` instead.
      """
  )
  public var graphAPIDebugParamValue: String? { graphAPIDebugParameterValue }

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

   @warning INTERNAL - DO NOT USE
   */
  public var graphAPIDebugParameterValue: String? {
    if loggingBehaviors.contains(.graphAPIDebugInfo) {
      return GraphAPIDebugParameterValue.info.rawValue
    } else if loggingBehaviors.contains(.graphAPIDebugWarning) {
      return GraphAPIDebugParameterValue.warning.rawValue
    } else {
      return nil
    }
  }

  // swiftlint:enable let_var_whitespace

  #if DEBUG
  func reset() {
    _loggingBehaviors = [.developerErrors]
    resetDependencies()
  }
  #endif
}

// MARK: - Dependencies

extension Settings: DependentAsObject {
  struct ObjectDependencies {
    var appEventsConfigurationProvider: _AppEventsConfigurationProviding
    var dataStore: DataPersisting
    var eventLogger: EventLogging
    var infoDictionaryProvider: InfoDictionaryProviding
  }

  func validateConfiguration() {
    #if DEBUG
    // swiftlint:disable:next statement_position
    do { _ = try getDependencies() }
    catch { fatalError(Self.unconfiguredDebugMessage) }
    #endif
  }

  static let unconfiguredDebugMessage = """
    As of v9.0, you must initialize the SDK prior to calling any methods or setting any properties. \
    You can do this by calling `ApplicationDelegate.application(_:didFinishLaunchingWithOptions:)`. \
    Learn more: https://developers.facebook.com/docs/ios/getting-started. \
    If no `UIApplication` instance is available, you can use `ApplicationDelegate.initializeSDK()`.
    """
}


/*
 * 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

/**
 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(FBSDKSettings)
public protocol SettingsProtocol {
  var appID: String? { get set }
  var clientToken: String? { get set }
  var userAgentSuffix: String? { get set }
  var sdkVersion: String { get }
  var displayName: String? { get set }
  var facebookDomainPart: String? { get set }
  var loggingBehaviors: Set<LoggingBehavior> { get set }
  var appURLSchemeSuffix: String? { get set }
  var isDataProcessingRestricted: Bool { get }
  var isAutoLogAppEventsEnabled: Bool { get }
  // swiftlint:disable:next swiftlint_disable_without_this_or_next
  // swiftlint:disable let_var_whitespace
  @available(
    *,
    deprecated,
    message: """
      This property is deprecated and will be removed in the next major release. \
      Use `isCodelessDebugLogEnabled` instead.
      """
  )
  var codelessDebugLogEnabled: Bool { get set }
  // swiftlint:enable let_var_whitespace
  var isCodelessDebugLogEnabled: Bool { get set }
  // swiftlint:disable:next swiftlint_disable_without_this_or_next
  // swiftlint:disable let_var_whitespace
  @available(
    *,
    deprecated,
    message: """
      This property is deprecated and will be removed in the next major release. \
      Use `isAdvertiserIDCollectionEnabled` instead.
      """
  )
  var advertiserIDCollectionEnabled: Bool { get set }
  // swiftlint:enable let_var_whitespace
  var isAdvertiserIDCollectionEnabled: Bool { get set }
  // swiftlint:disable:next swiftlint_disable_without_this_or_next
  // swiftlint:disable let_var_whitespace
  @available(
    *,
    deprecated,
    message: """
      This property is deprecated and will be removed in the next major release. \
      Use `isATETimeSufficientlyDelayed` instead.
      """
  )
  var isSetATETimeExceedsInstallTime: Bool { get }
  // swiftlint:enable let_var_whitespace
  var isATETimeSufficientlyDelayed: Bool { get }
  var isSKAdNetworkReportEnabled: Bool { get }
  var advertisingTrackingStatus: AdvertisingTrackingStatus { get }
  var installTimestamp: Date? { get }
  var advertiserTrackingEnabledTimestamp: Date? { get }
  var isEventDataUsageLimited: Bool { get set }
  var shouldUseTokenOptimizations: Bool { get set }
  var graphAPIVersion: String { get set }
  var isGraphErrorRecoveryEnabled: Bool { get set }
  // swiftlint:disable:next swiftlint_disable_without_this_or_next
  // swiftlint:disable let_var_whitespace
  @available(
    *,
    deprecated,
    message: """
      This property is deprecated and will be removed in the next major release. \
      Use `graphAPIDebugParameterValue` instead.
      """
  )
  var graphAPIDebugParamValue: String? { get }
  // swiftlint:enable let_var_whitespace
  var graphAPIDebugParameterValue: String? { get }
  // swiftlint:disable:next swiftlint_disable_without_this_or_next
  // swiftlint:disable let_var_whitespace
  @available(
    *,
    deprecated,
    message: """
      This property is deprecated and will be removed in the next major release. \
      Use `isAdvertiserTrackingEnabled` instead.
      """
  )
  var advertiserTrackingEnabled: Bool { get set }
  // swiftlint:enable let_var_whitespace
  var isAdvertiserTrackingEnabled: Bool { get set }
  var shouldUseCachedValuesForExpensiveMetadata: Bool { get set }
  var persistableDataProcessingOptions: [DataProcessingOptionKey.RawValue: Any]? { get }

  /**
   Sets the data processing options.

   - Parameter options: The list of options.
   */
  func setDataProcessingOptions(_ options: [String]?)

  /**
   Sets the data processing options.

   - Parameters:
     - options The list of the options.
     - country The code for the country.
     - state The code for the state.
   */
  func setDataProcessingOptions(_ options: [String]?, country: Int32, state: Int32)
}


/*
 * 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.
 */

/**
 Describe known values for dialog configurations.

 Internal Use Only
 */
public enum DialogConfigurationName {
  public static let message = "message"
  public static let share = "share"
}

/**
 A lightweight interface to expose aspects of ServerConfiguration that are used by dialogs in ShareKit.

 Internal Use Only
 */
public struct ShareDialogConfiguration {

  var configuredDependencies: ValueDependencies?

  var defaultDependencies: ValueDependencies? = .init(
    serverConfigurationProvider: _ServerConfigurationManager.shared
  )

  public init() {}

  public var defaultShareMode: String? {
    guard let dependencies = try? getDependencies() else { return nil }

    return dependencies.serverConfigurationProvider.cachedServerConfiguration().defaultShareMode
  }

  public func shouldUseNativeDialog(forDialogName dialogName: String) -> Bool {
    guard let dependencies = try? getDependencies() else { return false }

    return dependencies.serverConfigurationProvider
      .cachedServerConfiguration()
      .useNativeDialog(forDialogName: dialogName)
  }

  public func shouldUseSafariViewController(forDialogName dialogName: String) -> Bool {
    guard let dependencies = try? getDependencies() else { return false }

    return dependencies.serverConfigurationProvider
      .cachedServerConfiguration()
      .useSafariViewController(forDialogName: dialogName)
  }
}

extension ShareDialogConfiguration: DependentAsValue {
  struct ValueDependencies {
    var serverConfigurationProvider: _ServerConfigurationProviding
  }
}


/*
 * 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

/**
 Makes sure no other thread reenters the closure before the one running has not returned
 */
func synchronized(_ lock: AnyObject, closure: () throws -> Void) rethrows {
  objc_sync_enter(lock)
  defer { objc_sync_exit(lock) }
  try closure()
}


/*
 * 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 Comparable {
  func fb_clamped(to range: ClosedRange<Self>) -> Self {
    min(max(range.lowerBound, self), range.upperBound)
  }
}


/*
 * 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
import SafariServices

/**
 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 _BridgeAPI: NSObject,
  FBSDKApplicationObserving,
  URLOpener,
  BridgeAPIRequestOpening,
  _ContainerViewControllerDelegate,
  SFSafariViewControllerDelegate {

  private enum Values {
    static let authenticationSessionErrorDomain = "com.apple.AuthenticationServices.WebAuthenticationSession"
    static let sourceApplication = "com.apple"
    static let bridgeResponseHost = "bridge"
  }

  let logger: _Logger
  let urlOpener: _InternalURLOpener
  let bridgeAPIResponseFactory: BridgeAPIResponseCreating
  let appURLSchemeProvider: AppURLSchemeProviding
  let errorFactory: ErrorCreating
  var pendingRequest: BridgeAPIRequestProtocol?
  var pendingRequestCompletionBlock: BridgeAPIResponseBlock?
  var pendingURLOpener: URLOpening?
  var authenticationSession: AuthenticationSessionProtocol?
  var authenticationSessionCompletionHandler: AuthenticationCompletionHandler?
  var authenticationSessionState = AuthenticationSessionState.none
  var isExpectingBackground = false
  var safariViewController: SFSafariViewController?
  var isDismissingSafariViewController = false
  var isActive = false

  public static let shared = _BridgeAPI(
    logger: _Logger(loggingBehavior: .developerErrors),
    urlOpener: CoreUIApplication.shared,
    bridgeAPIResponseFactory: _BridgeAPIResponseFactory(),
    appURLSchemeProvider: InternalUtility.shared,
    errorFactory: _ErrorFactory()
  )

  init(
    logger: _Logger,
    urlOpener: _InternalURLOpener,
    bridgeAPIResponseFactory: BridgeAPIResponseCreating,
    appURLSchemeProvider: AppURLSchemeProviding,
    errorFactory: ErrorCreating
  ) {
    self.logger = logger
    self.urlOpener = urlOpener
    self.bridgeAPIResponseFactory = bridgeAPIResponseFactory
    self.appURLSchemeProvider = appURLSchemeProvider
    self.errorFactory = errorFactory
  }

  private func updateAuthStateIfSystemAlertToUseWebAuthFlowPresented() {
    if authenticationSession != nil,
       authenticationSessionState == .started {
      authenticationSessionState = .showAlert
    }
  }

  private func updateAuthStateIfSystemCancelAuthSession() {
    if authenticationSession != nil,
       authenticationSessionState == .showAlert {
      authenticationSessionState = .canceledBySystem
    }
  }

  private var isRequestingWebAuthenticationSession: Bool {
    ![.none, .canceledBySystem].contains(authenticationSessionState)
  }

  func bridgeAPIRequestCompletionBlock(
    request: BridgeAPIRequestProtocol,
    completion completionBlock: @escaping BridgeAPIResponseBlock
  ) -> SuccessBlock {
    { [self] openedURL, _ in
      guard !openedURL else { return }

      pendingRequest = nil
      pendingRequestCompletionBlock = nil
      let openedURLError: Error

      if request.scheme.hasPrefix(URLScheme.http.rawValue) {
        openedURLError = errorFactory.error(
          code: CoreError.errorBrowserUnavailable.rawValue,
          message: "the app switch failed because the browser is unavailable",
          underlyingError: nil
        )
      } else {
        openedURLError = errorFactory.error(
          code: CoreError.errorAppVersionUnsupported.rawValue,
          message: "the app switch failed because the destination app is out of date",
          underlyingError: nil
        )
      }
      let response = bridgeAPIResponseFactory.createResponse(request: request, error: openedURLError)
      completionBlock(response)
    }
  }

  private func presentSafariViewController(
    with url: URL,
    in container: _ContainerViewController,
    from parent: UIViewController
  ) {
    let safariController = SFSafariViewController(url: url)
    safariViewController = safariController

    // Disable dismissing with edge pan gesture
    safariController.modalPresentationStyle = .overFullScreen
    safariController.delegate = self
    container.displayChildController(safariController)
    parent.present(container, animated: true)
  }

  func openURLWithAuthenticationSession(url: URL) {
    if let session = authenticationSession {
      // swiftlint:disable:next line_length
      logger.logEntry("There is already a request for authenticated session. Cancelling active authentication session before starting the new one.")
      session.cancel()
    }

    authenticationSession = ASWebAuthenticationSession(
      url: url,
      callbackURLScheme: appURLSchemeProvider.appURLScheme,
      completionHandler: authenticationSessionCompletionHandler ?? { [weak self] _, _ in
        self?.clearSession()
      }
    )

    if #available(iOS 13, *) {
      authenticationSession?.presentationContextProvider = self
    }

    authenticationSessionState = .started
    _ = authenticationSession?.start()
  }

  func setSessionCompletionHandler(calling handler: @escaping SuccessBlock) {
    authenticationSessionCompletionHandler = { [weak self] potentialURL, potentialError in
      let didSucceed = (potentialError == nil && potentialURL != nil)
      handler(didSucceed, potentialError)

      if let url = potentialURL,
         didSucceed {
        _ = self?.application(
          UIApplication.shared,
          open: url,
          sourceApplication: Values.sourceApplication,
          annotation: nil
        )
      }
      self?.clearSession()
    }
  }

  func clearSession() {
    authenticationSession = nil
    authenticationSessionCompletionHandler = nil
    authenticationSessionState = .none
  }

  public func viewControllerDidDisappear(
    _ viewController: _ContainerViewController,
    animated: Bool
  ) {
    if let safariViewController = safariViewController {
      logger.logEntry(
        """
        **ERROR**:
        The SFSafariViewController's parent view controller was dismissed.
        This can happen if you are triggering login from a UIAlertController. Instead, make sure your topmost view \
        controller will not be prematurely dismissed.
        """
      )
      safariViewControllerDidFinish(safariViewController)
    }
  }

  func handleBridgeAPIResponse(
    url responseURL: URL,
    sourceApplication: String?
  ) -> Bool {
    let request = pendingRequest
    let completionBlock = pendingRequestCompletionBlock
    pendingRequest = nil
    pendingRequestCompletionBlock = nil

    guard
      responseURL.scheme == appURLSchemeProvider.appURLScheme,
      responseURL.host == Values.bridgeResponseHost,
      let request = request
    else {
      return false
    }

    guard let completionBlock = completionBlock else {
      return true
    }

    do {
      let response = try bridgeAPIResponseFactory.createResponse(
        request: request,
        responseURL: responseURL,
        sourceApplication: sourceApplication
      )
      completionBlock(response)
      return true
    } catch let error as NSError where error.code == CoreError.errorBridgeAPIResponse.rawValue {
      return false
    } catch {
      completionBlock(
        bridgeAPIResponseFactory.createResponse(request: request, error: error)
      )
      return true
    }
  }

  func cancelBridgeRequest() {
    if let request = pendingRequest {
      pendingRequestCompletionBlock?(BridgeAPIResponse(cancelledWith: request))
    }
    pendingRequest = nil
    pendingRequestCompletionBlock = nil
  }
}

// MARK: FBSDKApplicationObserving Conformance

extension _BridgeAPI {
  public func applicationWillResignActive(_ application: UIApplication?) {
    updateAuthStateIfSystemAlertToUseWebAuthFlowPresented()
  }

  public func applicationDidBecomeActive(_ application: UIApplication?) {
    var isRequestingWebAuthenticationSession = false

    if authenticationSession != nil {
      switch authenticationSessionState {
      case .none, .started, .showWebBrowser:
        break
      case .showAlert:
        authenticationSessionState = .showWebBrowser
      case .canceledBySystem:
        authenticationSession?.cancel()
        authenticationSession = nil
        let errorDomain = Values.authenticationSessionErrorDomain
        let error = errorFactory.error(domain: errorDomain, code: 1, message: nil, underlyingError: nil)

        authenticationSessionCompletionHandler?(nil, error)
        isRequestingWebAuthenticationSession = self.isRequestingWebAuthenticationSession
      }
    }

    // _expectingBackground can be YES if the caller started doing work (like login)
    // within the app delegate's lifecycle like openURL, in which case there
    // might have been a "didBecomeActive" event pending that we want to ignore.
    guard
      !isExpectingBackground,
      safariViewController == nil,
      !isDismissingSafariViewController,
      !isRequestingWebAuthenticationSession
    else { return }

    isActive = true

    if let validApplication = application {
      pendingURLOpener?.applicationDidBecomeActive(validApplication)
    }
    cancelBridgeRequest()

    NotificationCenter.default.post(name: .FBSDKApplicationDidBecomeActive, object: self)
  }

  public func applicationDidEnterBackground(_ application: UIApplication?) {
    isActive = false
    isExpectingBackground = false
    updateAuthStateIfSystemCancelAuthSession()
  }

  public func application(
    _ application: UIApplication,
    open url: URL,
    sourceApplication: String?,
    annotation: Any?
  ) -> Bool {
    let pendingURLOpener = self.pendingURLOpener

    if let pending = pendingURLOpener,
       pending.shouldStopPropagation?(of: url) == true {
      return true
    }

    let canOpenURL = pendingURLOpener?.canOpen(
      url,
      for: application,
      sourceApplication: sourceApplication,
      annotation: annotation
    ) ?? false

    let completePendingOpenURLBlock = { [self] in
      self.pendingURLOpener = nil
      pendingURLOpener?.application(
        application,
        open: url,
        sourceApplication: sourceApplication,
        annotation: annotation
      )
      isDismissingSafariViewController = false
    }
    // if they completed a SFVC flow, dismiss it.
    if let safariViewController = safariViewController {
      isDismissingSafariViewController = true
      safariViewController.presentingViewController?.dismiss(animated: true, completion: completePendingOpenURLBlock)
      self.safariViewController = nil
    } else {
      if authenticationSession != nil {
        authenticationSession?.cancel()
        authenticationSession = nil

        // This check is needed in case another sdk / message / ad etc... tries to open the app
        // during the login flow.
        // This dismisses the authentication browser without triggering any login callbacks.
        // Hence we need to explicitly call the authentication session's completion handler.
        if !canOpenURL {
          let errorMessage = "Login attempt cancelled by alternate call to openURL from: \(url)"
          let loginError = errorFactory.error(
            code: CoreError.errorBridgeAPIInterruption.rawValue,
            userInfo: [ErrorLocalizedDescriptionKey: errorMessage],
            message: errorMessage,
            underlyingError: nil
          )

          if let handler = authenticationSessionCompletionHandler {
            handler(url, loginError)
            authenticationSessionCompletionHandler = nil
          }
        }
      }
      completePendingOpenURLBlock()
    }

    if canOpenURL {
      return true
    }

    return handleBridgeAPIResponse(url: url, sourceApplication: sourceApplication)
  }

  public func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    guard
      let launchedURL = launchOptions?[UIApplication.LaunchOptionsKey.url] as? URL,
      let sourceApplication = launchOptions?[UIApplication.LaunchOptionsKey.sourceApplication] as? String,
      let loginManagerClass = NSClassFromString("FBSDKLoginManager") as? URLOpening.Type,
      let loginManager = loginManagerClass.makeOpener?()
    else {
      return false
    }

    return loginManager.application(
      application,
      open: launchedURL,
      sourceApplication: sourceApplication,
      annotation: launchOptions?[UIApplication.LaunchOptionsKey.annotation]
    )
  }
}

// MARK: URLOpener Conformance

extension _BridgeAPI {
  public func open(
    _ url: URL,
    sender: URLOpening?,
    handler: @escaping SuccessBlock
  ) {
    isExpectingBackground = true
    pendingURLOpener = sender

    let block = { [weak urlOpener, weak errorFactory] in
      // Dispatch openURL calls to prevent hangs if we're inside the current app delegate's openURL flow already
      if let opener = urlOpener {
        opener.open(url, options: [:]) { success in
          handler(success, nil)
        }
      } else {
        #if DEBUG
        // self.urlOpener should only be nil in test
        let message = "Cannot login due to urlOpener being nil"
        let userInfo = [ErrorLocalizedDescriptionKey: message]
        let loginError = errorFactory?.unknownError(message: message, userInfo: userInfo)
        handler(false, loginError)
        #endif
      }
    }

    #if DEBUG
    block()
    #else
    DispatchQueue.main.async(execute: block)
    #endif
  }

  public func open(
    _ request: BridgeAPIRequestProtocol,
    useSafariViewController: Bool,
    from fromViewController: UIViewController?,
    completionBlock: @escaping BridgeAPIResponseBlock
  ) {
    do {
      let requestURL = try request.requestURL()

      pendingRequest = request
      pendingRequestCompletionBlock = completionBlock
      let handler = bridgeAPIRequestCompletionBlock(request: request, completion: completionBlock)

      if useSafariViewController {
        openURLWithSafariViewController(url: requestURL, sender: nil, from: fromViewController, handler: handler)
      } else {
        open(requestURL, sender: nil, handler: handler)
      }
    } catch {
      let response = bridgeAPIResponseFactory.createResponse(request: request, error: error)
      completionBlock(response)
      return
    }
  }

  public func openURLWithSafariViewController(
    url: URL,
    sender: URLOpening?,
    from fromViewController: UIViewController?,
    handler: @escaping SuccessBlock
  ) {
    guard url.scheme?.hasPrefix(URLScheme.http.rawValue) == true else {
      return open(url, sender: sender, handler: handler)
    }

    isExpectingBackground = false
    pendingURLOpener = sender

    if sender?.isAuthenticationURL(url) == true {
      setSessionCompletionHandler(calling: handler)
      openURLWithAuthenticationSession(url: url)
      return
    }

    guard let parent = fromViewController ?? InternalUtility.shared.topMostViewController() else {
      logger.logEntry("There are no valid ViewController to present SafariViewController with")
      return
    }

    var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
    let sfvcQueryItem = URLQueryItem(name: "sfvc", value: "1")
    let items = components?.queryItems ?? []
    components?.queryItems = items + [sfvcQueryItem]

    guard let updatedURL = components?.url else {
      logger.logEntry("Unable to create a URL from URL components.")
      return
    }

    let container = _ContainerViewController()
    container.delegate = self
    if let transitionCoordinator = parent.transitionCoordinator {
      // Wait until the transition is finished before presenting SafariVC to avoid a blank screen.
      transitionCoordinator.animate(alongsideTransition: nil) { [self] _ in
        // Note SFVC init must occur inside block to avoid blank screen.
        presentSafariViewController(with: updatedURL, in: container, from: parent)
      }
    } else {
      presentSafariViewController(with: updatedURL, in: container, from: parent)
    }
    // Assuming Safari View Controller always opens
    handler(true, nil)
  }
}

// MARK: SFSafariViewControllerDelegate Conformance

extension _BridgeAPI {
  // This means the user tapped "Done" which we should treat as a cancellation.
  public func safariViewControllerDidFinish(_ safariViewController: SFSafariViewController) {
    if let opener = pendingURLOpener {
      pendingURLOpener = nil
      opener.application(nil, open: nil, sourceApplication: nil, annotation: nil)
    }
    cancelBridgeRequest()
    self.safariViewController = nil
  }
}

// MARK: ASWebAuthenticationPresentationContextProviding Conformance

@available(iOS 13, *)
extension _BridgeAPI: ASWebAuthenticationPresentationContextProviding {
  public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
    UIApplication.shared.keyWindow ?? ASPresentationAnchor()
  }
}


/*
 * 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

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

 @warning INTERNAL - DO NOT USE
 */

@objcMembers
@objc(FBSDKMeasurementEvent)
public final class _MeasurementEvent: NSObject, _AppLinkEventPosting {

  /// Defines keys in the userInfo object for the notification named `measurementEvent`
  private enum Keys {

    /// The string field for the name of the event
    static let name = "event_name"

    /// The dictionary field for the arguments of the event
    static let arguments = "event_args"
  }

  @objc(postNotificationForEventName:args:)
  public func postNotification(eventName: String, arguments: [String: Any]) {

    let notificationCenter = NotificationCenter.default
    let userInfo: [String: Any] = [
      Keys.name: eventName,
      Keys.arguments: arguments,
    ]

    notificationCenter.post(name: .MeasurementEvent, object: self, userInfo: userInfo)
  }
}


/*
 * 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 FBAEMKit
import Foundation

/**
 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(FBSDKAEMReporter)
public protocol _AEMReporterProtocol {
  static func enable()

  @objc(recordAndUpdateEvent:currency:value:parameters:)
  static func recordAndUpdate(
    event: String,
    currency: String?,
    value: NSNumber?,
    parameters: [String: Any]?
  )

  static func setConversionFilteringEnabled(_ isEnabled: Bool)

  static func setCatalogMatchingEnabled(_ isEnabled: Bool)

  static func setAdvertiserRuleMatchInServerEnabled(_ isEnabled: Bool)

  static func handle(_ url: URL?)
}

extension AEMReporter: _AEMReporterProtocol {}


/*
 * 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 FBSDKCoreKit_Basics

@objc(FBSDKCAPIReporter)
public protocol CAPIReporter {
  func enable()

  func configure(factory: GraphRequestFactoryProtocol, settings: SettingsProtocol)

  func recordEvent(_ parameters: [String: Any])
}


/*
 * 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

// MARK: App parameters

enum AppEventName: String {
  case unlockedAchievement = "fb_mobile_achievement_unlocked"
  case activatedApp = "fb_mobile_activate_app"
  case addedPaymentInfo = "fb_mobile_add_payment_info"
  case addedToCart = "fb_mobile_add_to_cart"
  case addedToWishlist = "fb_mobile_add_to_wishlist"
  case completedRegistration = "fb_mobile_complete_registration"
  case viewedContent = "fb_mobile_content_view"
  case initiatedCheckout = "fb_mobile_initiated_checkout"
  case achievedLevel = "fb_mobile_level_achieved"
  case purchased = "fb_mobile_purchase"
  case rated = "fb_mobile_rate"
  case searched = "fb_mobile_search"
  case spentCredits = "fb_mobile_spent_credits"
  case completedTutorial = "fb_mobile_tutorial_completion"
}

enum CustomEventField: String {
  case eventTime = "_logTime"
  case eventName = "_eventName"
  case valueToSum = "_valueToSum"
  case contentIds = "content_ids"
  case contents = "fb_content_id"
  case contentType = "fb_content_type"
  case description = "fb_description"
  case level = "fb_level"
  case maxRatingValue = "fb_max_rating_value"
  case numItems = "fb_num_items"
  case paymentInfoAvailable = "fb_payment_info_available"
  case registrationMethod = "fb_registration_method"
  case searchString = "fb_search_string"
  case success = "fb_success"
  case orderId = "fb_order_id"
  case adType = "ad_type"
  case currency = "fb_currency"
}

enum AppEventType: String {
  case mobileAppInstall
  case custom
  case other

  init(rawValue: String) {
    switch rawValue {
    case "MOBILE_APP_INSTALL": self = .mobileAppInstall
    case "CUSTOM_APP_EVENTS": self = .custom
    default: self = .other
    }
  }
}

enum AppEventUserAndAppDataField: String {
  // user data fields
  case anonId = "anon_id"
  case appUserId = "app_user_id"
  case advertiserId = "advertiser_id"
  case pageId = "page_id"
  case pageScopedUserId = "page_scoped_user_id"
  case userData = "ud"

  // app data fields
  case advTE = "advertiser_tracking_enabled"
  case appTE = "application_tracking_enabled"
  case considerViews = "consider_views"
  case deviceToken = "device_token"
  case extinfo
  case includeDwellData = "include_dwell_data"
  case includeVideoData = "include_video_data"
  case installReferrer = "install_referrer"
  case installerPackage = "installer_package"
  case receiptData = "receipt_data"
  case urlSchemes = "url_schemes"
  case campaignIds = "campaign_ids"
}

// MARK: ConversionsAPI parameters

enum ConversionsAPISection: String {
  case userData = "user_data"
  case appData = "app_data"
  case customData = "custom_data"
  case customEvents = "custom_events"
}

enum ConversionsAPICustomEventField: String {
  case valueToSum = "value"
  case eventTime = "event_time"
  case eventName = "event_name"
  case contentIds = "content_ids"
  case contents
  case contentType = "content_type"
  case description
  case level
  case maxRatingValue = "max_rating_value"
  case numItems = "num_items"
  case paymentInfoAvailable = "payment_info_available"
  case registrationMethod = "registration_method"
  case searchString = "search_string"
  case success
  case orderId = "order_id"
  case adType = "ad_type"
  case currency
}

enum ConversionsAPIUserAndAppDataField: String {
  // user data fields
  case anonId = "anon_id"
  case fbLoginId = "fb_login_id"
  case madid
  case pageId = "page_id"
  case pageScopedUserId = "page_scoped_user_id"
  case userData = "ud"

  // app data fields
  case advTE = "advertiser_tracking_enabled"
  case appTE = "application_tracking_enabled"
  case considerViews = "consider_views"
  case deviceToken = "device_token"
  case extinfo
  case includeDwellData = "include_dwell_data"
  case includeVideoData = "include_video_data"
  case installReferrer = "install_referrer"
  case installerPackage = "installer_package"
  case receiptData = "receipt_data"
  case urlSchemes = "url_schemes"
  case campaignIds = "campaign_ids"
}

enum ConversionsAPIEventName: String {
  case achievementUnlocked = "AchievementUnlocked"
  case activateApp = "ActivateApp"
  case addPaymentInfo = "AddPaymentInfo"
  case addToCart = "AddToCart"
  case addToWishlist = "AddToWishlist"
  case completeRegistration = "CompleteRegistration"
  case viewContent = "ViewContent"
  case initiateCheckout = "InitiateCheckout"
  case levelAchieved = "LevelAchieved"
  case purchase = "Purchase"
  case rate = "Rate"
  case search = "Search"
  case spentCredits = "SpentCredits"
  case tutorialCompletion = "TutorialCompletion"
}

enum OtherEventConstants: String {
  case event
  case actionSource = "action_source"
  case app
  case mobileAppInstall = "MobileAppInstall"
  case installEventTime = "install_timestamp"
}

// MARK: App Events to Conversions API Transformer dictionaries

enum AppEventsConversionsAPITransformer {

  struct SectionFieldMapping {
    let section: ConversionsAPISection
    let field: ConversionsAPIUserAndAppDataField?
  }

  static let topLevelTransformations: [AppEventUserAndAppDataField: SectionFieldMapping] = [
    // user_data mapping
    .anonId: .init(section: .userData, field: .anonId),
    .appUserId: .init(section: .userData, field: .fbLoginId),
    .advertiserId: .init(section: .userData, field: .madid),
    .pageId: .init(section: .userData, field: .pageId),
    .pageScopedUserId: .init(section: .userData, field: .pageScopedUserId),

    // app_data mapping
    .advTE: .init(section: .appData, field: .advTE),
    .appTE: .init(section: .appData, field: .appTE),
    .considerViews: .init(section: .appData, field: .considerViews),
    .deviceToken: .init(section: .appData, field: .deviceToken),
    .extinfo: .init(section: .appData, field: .extinfo),
    .includeDwellData: .init(section: .appData, field: .includeDwellData),
    .includeVideoData: .init(section: .appData, field: .includeVideoData),
    .installReferrer: .init(section: .appData, field: .installReferrer),
    .installerPackage: .init(section: .appData, field: .installerPackage),
    .receiptData: .init(section: .appData, field: .receiptData),
    .urlSchemes: .init(section: .appData, field: .urlSchemes),
    .campaignIds: .init(section: .appData, field: .campaignIds),
    .userData: .init(section: .userData, field: nil),
  ]

  struct SectionCustomEventFieldMapping {
    let section: ConversionsAPISection?
    let field: ConversionsAPICustomEventField
  }

  static let customEventTransformations: [CustomEventField: SectionCustomEventFieldMapping] = [
    // custom_events mapping
    .eventTime: .init(section: nil, field: .eventTime),
    .eventName: .init(section: nil, field: .eventName),
    .valueToSum: .init(section: .customData, field: .valueToSum),
    .contentIds: .init(section: .customData, field: .contentIds), // string to array conversion required
    // string to array conversion required, contents has an extra field: price
    .contents: .init(section: .customData, field: .contents),
    .contentType: .init(section: .customData, field: .contentType),
    .currency: .init(section: .customData, field: .currency),
    .description: .init(section: .customData, field: .description),
    .level: .init(section: .customData, field: .level),
    .maxRatingValue: .init(section: .customData, field: .maxRatingValue),
    .numItems: .init(section: .customData, field: .numItems),
    .paymentInfoAvailable: .init(section: .customData, field: .paymentInfoAvailable),
    .registrationMethod: .init(section: .customData, field: .registrationMethod),
    .searchString: .init(section: .customData, field: .searchString),
    .success: .init(section: .customData, field: .success),
    .orderId: .init(section: .customData, field: .orderId),
    .adType: .init(section: .customData, field: .adType),
  ]

  static let standardEventTransformations: [AppEventName: ConversionsAPIEventName] = [
    .unlockedAchievement: .achievementUnlocked,
    .activatedApp: .activateApp,
    .addedPaymentInfo: .addPaymentInfo,
    .addedToCart: .addToCart,
    .addedToWishlist: .addToWishlist,
    .completedRegistration: .completeRegistration,
    .viewedContent: .viewContent,
    .initiatedCheckout: .initiateCheckout,
    .achievedLevel: .levelAchieved,
    .purchased: .purchase,
    .rated: .rate,
    .searched: .search,
    .spentCredits: .spentCredits,
    .completedTutorial: .tutorialCompletion,
  ]

  enum DataProcessingParameterName: String, CaseIterable {
    case options = "data_processing_options"
    case country = "data_processing_options_country"
    case state = "data_processing_options_state"
  }

  enum ValueTransformationType: String, CaseIterable {
    case array
    case bool
    case int

    init?(field: String) {
      switch field {
      case AppEventUserAndAppDataField.extinfo.rawValue: self = .array
      case AppEventUserAndAppDataField.urlSchemes.rawValue: self = .array
      case CustomEventField.contentIds.rawValue: self = .array
      case CustomEventField.contents.rawValue: self = .array
      case DataProcessingParameterName.options.rawValue: self = .array
      case AppEventUserAndAppDataField.advTE.rawValue: self = .bool
      case AppEventUserAndAppDataField.appTE.rawValue: self = .bool
      case CustomEventField.eventTime.rawValue: self = .int
      default: return nil
      }
    }
  }

  static func transformValue(field: String, value: Any) -> Any? {
    guard let type = ValueTransformationType(field: field),
          let param = value as? String,
          let data = param.data(using: .utf8)
    else {
      return value
    }

    switch type {
    case .array:
      guard let array = try? JSONSerialization.jsonObject(with: data, options: []) as? [Any]
      else {
        return value
      }
      return array
    case .bool:
      guard let coercedInteger = Int(param) else {
        return value
      }
      return coercedInteger != 0
    case .int:
      guard let int = value as? Int ?? Int(value as? String ?? "") else {
        return nil
      }
      return int
    }
  }

  // MARK: split app events parameters into user, app data and custom events

  static func splitAppEventParameters(
    from parameters: [String: Any],
    userData: inout [String: Any],
    appData: inout [String: Any],
    restOfData: inout [String: Any],
    customEvents: inout [[String: Any]]
  ) -> AppEventType? {

    guard let eventTypeRaw = parameters[OtherEventConstants.event.rawValue] as? String,
          let eventType = AppEventType(rawValue: eventTypeRaw) as AppEventType?,
          eventType != .other else {
      return .other
    }

    parameters.forEach { key, value in
      if let field = AppEventUserAndAppDataField(rawValue: key) {
        transformAndUpdateAppAndUserData(userData: &userData, appData: &appData, field: field, value: value)
      } else if let rawEvents = value as? String,
                key == ConversionsAPISection.customEvents.rawValue,
                eventType == .custom,
                let events = transformEvents(from: rawEvents) {
        customEvents = events
      } else if DataProcessingParameterName(rawValue: key) != nil {
        restOfData[key] = transformValue(field: key, value: value)
      }
    }
    return eventType
  }

  // MARK: user and app data transformations

  static func transformAndUpdateAppData(
    _ appData: inout [String: Any],
    field: AppEventUserAndAppDataField,
    value: Any
  ) {
    guard let key = topLevelTransformations[field]?.field?.rawValue else {
      return
    }
    appData[key] = transformValue(field: field.rawValue, value: value)
  }

  static func transformAndUpdateUserData(
    _ userData: inout [String: Any],
    field: AppEventUserAndAppDataField,
    value: Any
  ) {
    if field == .userData {
      guard let udParam = value as? String,
            let data = udParam.data(using: .utf8),
            let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
      else {
        return
      }
      userData.merge(dictionary) { $1 }
    } else {
      guard let key = topLevelTransformations[field]?.field?.rawValue else {
        return
      }
      userData[key] = transformValue(field: field.rawValue, value: value)
    }
  }

  static func transformAndUpdateAppAndUserData(
    userData: inout [String: Any],
    appData: inout [String: Any],
    field: AppEventUserAndAppDataField,
    value: Any
  ) {
    guard let section = topLevelTransformations[field]?.section else {
      return
    }
    switch section {
    case .appData:
      transformAndUpdateAppData(&appData, field: field, value: value)
    case .userData:
      transformAndUpdateUserData(&userData, field: field, value: value)
    default:
      return
    }
  }

  // MARK: events section transformations

  static func transformEventName(from rawEventName: String) -> String {
    guard let eventName = AppEventName(rawValue: rawEventName) else {
      return rawEventName
    }
    return standardEventTransformations[eventName]?.rawValue ?? ""
  }

  static func transformEvents(
    from appEvents: String
  ) -> [[String: Any]]? {
    guard let data = appEvents.data(using: .utf8),
          let events = try? JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]],
          !appEvents.isEmpty
    else {
      return nil
    }

    var transformedEvents = [[String: Any]]()
    events.forEach { event in
      var customData = [String: Any]()
      var transformedEvent = [String: Any]()
      event.keys.forEach { key in
        guard let keyEnum = CustomEventField(rawValue: key),
              let mapping = customEventTransformations[keyEnum] else {
          return
        }
        if let section = mapping.section {
          if section == .customData {
            customData[mapping.field.rawValue] = transformValue(
              field: key,
              value: event[key] as Any
            )
          }
        } else if keyEnum == CustomEventField.eventName,
                  let eventName = event[key] as? String {
          transformedEvent[ConversionsAPICustomEventField.eventName.rawValue] = transformEventName(
            from: eventName)
        } else if keyEnum == CustomEventField.eventTime {
          transformedEvent[ConversionsAPICustomEventField.eventTime.rawValue] = transformValue(
            field: key,
            value: event[key] as Any
          )
        }
      }
      if !customData.isEmpty {
        transformedEvent[ConversionsAPISection.customData.rawValue] = customData
      }
      transformedEvents.append(transformedEvent)
    }
    return transformedEvents
  }

  // MARK: combine transformed data

  static func combineCommonFields(
    userData: [String: Any],
    appData: [String: Any],
    restOfData: [String: Any]
  ) -> [String: Any] {
    var converted = [String: Any]()
    converted[OtherEventConstants.actionSource.rawValue] = OtherEventConstants.app.rawValue
    converted[ConversionsAPISection.userData.rawValue] = userData
    converted[ConversionsAPISection.appData.rawValue] = appData
    converted.merge(restOfData) { $1 }
    return converted
  }

  static func combineAllTransformedDataForMobileAppInstall(
    commonFields: [String: Any],
    eventTime: Int?
  ) -> [[String: Any]]? {
    guard eventTime != nil else {
      return nil
    }
    var transformedEvent = [String: Any]()
    transformedEvent.merge(commonFields) { $1 }
    transformedEvent[ConversionsAPICustomEventField.eventName.rawValue] = OtherEventConstants.mobileAppInstall.rawValue
    transformedEvent[ConversionsAPICustomEventField.eventTime.rawValue] = eventTime
    return [transformedEvent]
  }

  static func combineAllTransformedDataForCustom(
    commonFields: [String: Any],
    customEvents: [[String: Any]]
  ) -> [[String: Any]]? {
    guard !customEvents.isEmpty else {
      return nil
    }
    var transformedEvents = [[String: Any]]()
    customEvents.forEach { customEvent in
      var customEventTransformed = [String: Any]()
      customEventTransformed.merge(commonFields) { $1 }
      customEventTransformed.merge(customEvent) { $1 }
      transformedEvents.append(customEventTransformed)
    }
    return transformedEvents
  }

  // swiftlint:disable:next function_parameter_count
  static func combineAllTransformedData(
    eventType: AppEventType,
    userData: [String: Any],
    appData: [String: Any],
    restOfData: [String: Any],
    customEvents: [[String: Any]],
    eventTime: Int?
  ) -> [[String: Any]]? {
    let commonFields = combineCommonFields(userData: userData, appData: appData, restOfData: restOfData)
    switch eventType {
    case .mobileAppInstall:
      return combineAllTransformedDataForMobileAppInstall(commonFields: commonFields, eventTime: eventTime)

    case .custom:
      return combineAllTransformedDataForCustom(commonFields: commonFields, customEvents: customEvents)
    default:
      return nil
    }
  }

  // MARK: main function

  static func conversionsAPICompatibleEvent(from parameters: [String: Any]) -> [[String: Any]]? {
    var userData = [String: Any]()
    var appData = [String: Any]()
    var restOfData = [String: Any]()
    var customEvents = [[String: Any]]()

    guard let eventType = splitAppEventParameters(
      from: parameters,
      userData: &userData,
      appData: &appData,
      restOfData: &restOfData,
      customEvents: &customEvents
    ), eventType != .other else {
      return nil
    }

    return combineAllTransformedData(
      eventType: eventType,
      userData: userData,
      appData: appData,
      restOfData: restOfData,
      customEvents: customEvents,
      eventTime: parameters[OtherEventConstants.installEventTime.rawValue] as? Int
    )
  }
}


/*
 * 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 FBSDKCoreKit_Basics
import UIKit

enum SettingsAPIFields: String {
  case url = "endpoint"
  case enabled = "is_enabled"
  case datasetID = "dataset_id"
  case accessKey = "access_key"
}

public typealias AppEventsCAPIManager = FBSDKAppEventsCAPIManager
public typealias CAPIGBlock = (Bool) -> Void

@objcMembers
public final class FBSDKAppEventsCAPIManager: NSObject, CAPIReporter {
  private static let settingsPath = "cloudbridge_settings"
  private static let serialQueue = DispatchQueue(label: "capi_gateway_queue")

  public static let shared = FBSDKAppEventsCAPIManager()

  internal var isSDKGKEnabled = false
  internal var factory: GraphRequestFactoryProtocol?
  internal var settings: SettingsProtocol?
  internal var configRefreshTimestamp: Date?
  internal var isLoadingConfig = false
  internal var completionBlocks: [CAPIGBlock] = []

  private enum TimeIntervals {
    static let configTimeout: TimeInterval = 86400
  }

  public override init() {}

  public func configure(factory: GraphRequestFactoryProtocol, settings: SettingsProtocol) {
    isSDKGKEnabled = false
    self.factory = factory
    self.settings = settings
  }

  public func enable() {
    isSDKGKEnabled = true
  }

  public func recordEvent(_ parameters: [String: Any]) {
    if isSDKGKEnabled {
      load { isCAPIGEnabled in
        if isCAPIGEnabled {
          FBSDKTransformerGraphRequestFactory.shared.callCapiGatewayAPI(
            with: parameters,
            userAgent: "FBiOSSDK.\(self.settings?.sdkVersion ?? "Unknown")"
          )
        }
      }
    }
  }

  internal func load(withBlock block: @escaping (Bool) -> Void) {
    guard let appID = settings?.appID else {
      print("App ID is not valid")
      block(false)
      return
    }

    FBSDKAppEventsCAPIManager.dispatchOnQueue(FBSDKAppEventsCAPIManager.serialQueue) {
      self.completionBlocks.append(block)

      if !self.shouldRefresh() {
        self.executeBlocks(isEnabled: true)
        return
      }

      if self.isLoadingConfig {
        return
      }

      self.isLoadingConfig = true
      guard let graphRequest = self.factory?.createGraphRequest(
        withGraphPath: String(format: "%@/%@", appID, FBSDKAppEventsCAPIManager.settingsPath),
        parameters: [:] as [String: Any],
        flags: []
      ) else {
        print("Fail to create CAPI Gateway Settings API request")
        self.executeBlocks(isEnabled: false)
        self.isLoadingConfig = false
        return
      }
      graphRequest.start { _, result, error in
        FBSDKAppEventsCAPIManager.dispatchOnQueue(FBSDKAppEventsCAPIManager.serialQueue) {
          if let error = error {
            print(error.localizedDescription)
            self.executeBlocks(isEnabled: false)
            self.isLoadingConfig = false
            return
          }

          guard
            let res = result as? [String: Any],
            let data = res["data"] as? [[String: Any]],
            let configuration = data.first
          else {
            print("CAPI Gateway Settings API response is not a valid json")
            self.executeBlocks(isEnabled: false)
            self.isLoadingConfig = false
            return
          }

          guard
            let url = TypeUtility.dictionary(
              configuration,
              objectForKey: SettingsAPIFields.url.rawValue,
              ofType: NSString.self
            ) as? String,
            let datasetID = TypeUtility.dictionary(
              configuration,
              objectForKey: SettingsAPIFields.datasetID.rawValue,
              ofType: NSString.self
            ) as? String,
            let accessKey = TypeUtility.dictionary(
              configuration,
              objectForKey: SettingsAPIFields.accessKey.rawValue,
              ofType: NSString.self
            ) as? String
          else {
            print("CAPI Gateway Settings API response doesn't have valid data")
            self.executeBlocks(isEnabled: false)
            self.isLoadingConfig = false
            return
          }

          FBSDKTransformerGraphRequestFactory.shared.configure(datasetID: datasetID, url: url, accessKey: accessKey)
          let isCAPIGEnabled = TypeUtility.boolValue(
            TypeUtility.dictionary(
              configuration,
              objectForKey: SettingsAPIFields.enabled.rawValue,
              ofType: NSNumber.self
            ) ?? false
          )
          self.executeBlocks(isEnabled: isCAPIGEnabled)
          self.isLoadingConfig = false
        }
      }
    }
  }

  func shouldRefresh() -> Bool {
    // We should refresh the config if
    // 1. refresh timestamp is expired
    // 2. local config doesn't exist
    return !isRefreshTimestampValid() ||
      FBSDKTransformerGraphRequestFactory.shared.credentials == nil
  }

  func isRefreshTimestampValid() -> Bool {
    guard let timestamp = configRefreshTimestamp else {
      return false
    }
    return Date().timeIntervalSince(timestamp) < TimeIntervals.configTimeout
  }

  func executeBlocks(isEnabled: Bool) {
    for block in completionBlocks {
      block(isEnabled)
    }
    completionBlocks = []
  }

  private static func dispatchOnQueue(
    _ queue: DispatchQueue,
    block: @escaping (() -> Void)
  ) {
    #if DEBUG
    block()
    #else
    queue.async(execute: block)
    #endif
  }
}


/*
 * 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 FBSDKCoreKit_Basics

@objcMembers
// swiftlint:disable:next prefer_final_classes
public class FBSDKTransformerGraphRequestFactory: NSObject {
  let contentType = "application/json"
  let timeoutInterval = 60
  let maxCachedEvents = 1000
  let maxProcessedEvents = 10

  let retryEventsHttpResponse = [
    -1009, // kCFURLErrorNotConnectedToInternet
    -1004, // kCFURLErrorCannotConnectToHost
    429, // CAPI-G Too many request
    503, // ServiceUnavailable
    504, // GatewayTimeout
  ]

  public static let shared = FBSDKTransformerGraphRequestFactory()

  private let serialQueue: DispatchQueue
  private let factory = GraphRequestFactory()

  public private(set) var credentials: CapiGCredentials?
  internal var transformedEvents: [[String: Any]] = []

  public struct CapiGCredentials {
    public let accessKey: String
    public let capiGatewayURL: String
    public let datasetID: String
  }

  public override init() {
    serialQueue = DispatchQueue(label: "com.facebook.appevents.CAPIGateway")
    super.init()
  }

  public func configure(datasetID: String, url: String, accessKey: String) {
    credentials = CapiGCredentials(accessKey: accessKey, capiGatewayURL: url, datasetID: datasetID)
  }

  public func callCapiGatewayAPI(with parameters: [String: Any], userAgent: String) {
    serialQueue.async { [weak self] in
      guard let self = self,
            let cbEndpoint = self.capiGatewayEndpoint(),
            let url = URL(string: cbEndpoint) else {
        return
      }

      do {
        let transformed = AppEventsConversionsAPITransformer.conversionsAPICompatibleEvent(from: parameters)
        self.appendEvents(events: transformed)
        let count = min(self.transformedEvents.count, self.maxProcessedEvents)
        let processedEvents = Array(self.transformedEvents[0 ..< count])
        self.transformedEvents.removeSubrange(0 ..< count)

        let requestDictionary = self.capiGatewayRequestDictionary(with: processedEvents)
        let jsonData = try JSONSerialization.data(withJSONObject: requestDictionary, options: [])

        var request = URLRequest(
          url: url,
          cachePolicy: .useProtocolCachePolicy,
          timeoutInterval: TimeInterval(self.timeoutInterval)
        )
        request.httpMethod = HTTPMethod.post.rawValue
        request.setValue(self.contentType, forHTTPHeaderField: "Content-Type")
        request.httpShouldHandleCookies = false
        request.httpBody = jsonData
        request.setValue(userAgent, forHTTPHeaderField: "User-Agent")

        URLSession.shared.dataTask(with: request) { _, response, error in
          self.serialQueue.async {
            guard error == nil,
                  let httpResponse = response as? HTTPURLResponse,
                  200 ... 299 ~= httpResponse.statusCode else {
              self.handleError(response: response, events: processedEvents)
              return
            }
          }
        }
        .resume()
      } catch {
        return
      }
    }
  }

  internal func capiGatewayRequestDictionary(with events: [[String: Any]]) -> [String: Any] {
    guard let accessKey = credentials?.accessKey else { return [:] }

    return [
      "data": events,
      "accessKey": accessKey,
    ]
  }

  private func capiGatewayEndpoint() -> String? {
    guard let credentials = credentials else { return nil }

    return String(format: "%@/capi/%@/events", credentials.capiGatewayURL, credentials.datasetID)
  }

  internal func handleError(response: URLResponse?, events: [[String: Any]]?) {
    // If it's not server error, we'll re-append the events to the event queue
    let response = response as? HTTPURLResponse
    if let statusCode = response?.statusCode {
      if !retryEventsHttpResponse.contains(statusCode) {
        return
      }
    }
    transformedEvents.insert(contentsOf: events ?? [], at: 0)
  }

  internal func appendEvents(events: [[String: Any]]?) {
    transformedEvents.append(contentsOf: events ?? [])
    let remainingEventsCount = transformedEvents.count - maxCachedEvents
    if remainingEventsCount > 0 {
      transformedEvents.removeSubrange(0 ..< remainingEventsCount)
    }
  }
}


/*
 * 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.
 */

final class AppEventsStateFactory: _AppEventsStateProviding {
  func createState(tokenString: String, appID: String) -> _AppEventsState {
    _AppEventsState(token: tokenString, appID: appID)
  }
}


/*
 * 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 StoreKit

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

 @warning INTERNAL - DO NOT USE

 Class to encapsulate implicit logging of purchase events
 */
@objc(FBSDKPaymentObserver)
public final class _PaymentObserver: NSObject, _PaymentObserving {
  var isObservingTransactions = false
  let paymentQueue: SKPaymentQueue
  let requestorFactory: _PaymentProductRequestorCreating

  @objc(initWithPaymentQueue:paymentProductRequestorFactory:)
  public init(paymentQueue: SKPaymentQueue, paymentProductRequestorFactory: _PaymentProductRequestorCreating) {
    self.paymentQueue = paymentQueue
    requestorFactory = paymentProductRequestorFactory
    super.init()
  }

  @objc(startObservingTransactions)
  public func startObservingTransactions() {
    synchronized(self) {
      if !isObservingTransactions {
        paymentQueue.add(self)
        isObservingTransactions = true
      }
    }
  }

  @objc(stopObservingTransactions)
  public func stopObservingTransactions() {
    synchronized(self) {
      if isObservingTransactions {
        paymentQueue.remove(self)
        isObservingTransactions = false
      }
    }
  }
}

extension _PaymentObserver: SKPaymentTransactionObserver {

  public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    for transaction in transactions {
      switch transaction.transactionState {
      case .purchasing, .purchased, .failed, .restored:
        handleTransaction(transaction)
      case .deferred:
        break
      @unknown default:
        break
      }
    }
  }

  func handleTransaction(_ transaction: SKPaymentTransaction) {
    let productRequestor = requestorFactory.createRequestor(transaction: transaction)
    productRequestor.resolveProducts()
  }
}


/*
 * 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

/**
 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(FBSDKPaymentProductRequestorFactory)
public final class _PaymentProductRequestorFactory: NSObject, _PaymentProductRequestorCreating {

  public func createRequestor(transaction: SKPaymentTransaction) -> PaymentProductRequestor {
    guard let dependencies = try? Self.getDependencies() else {
      fatalError("Dependencies have not been set")
    }

    return PaymentProductRequestor(
      transaction: transaction,
      settings: dependencies.settings,
      eventLogger: dependencies.eventLogger,
      gateKeeperManager: dependencies.gateKeeperManager,
      store: dependencies.store,
      loggerFactory: dependencies.loggerFactory,
      productsRequestFactory: dependencies.productsRequestFactory,
      appStoreReceiptProvider: dependencies.appStoreReceiptProvider
    )
  }
}

extension _PaymentProductRequestorFactory: DependentAsType {
  struct TypeDependencies {
    var settings: SettingsProtocol
    var eventLogger: EventLogging
    var gateKeeperManager: _GateKeeperManaging.Type
    var store: DataPersisting
    var loggerFactory: _LoggerCreating
    var productsRequestFactory: _ProductsRequestCreating
    var appStoreReceiptProvider: _AppStoreReceiptProviding
  }

  static var configuredDependencies: TypeDependencies?

  static var defaultDependencies: TypeDependencies? = TypeDependencies(
    settings: Settings.shared,
    eventLogger: AppEvents.shared,
    gateKeeperManager: _GateKeeperManager.self,
    store: UserDefaults.standard,
    loggerFactory: _LoggerFactory(),
    productsRequestFactory: _ProductRequestFactory(),
    appStoreReceiptProvider: Bundle(for: ApplicationDelegate.self)
  )
}


/*
 * 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

final class AEMNetworker: NSObject, AEMNetworking {
  func startGraphRequest(
    withGraphPath graphPath: String,
    parameters: [String: Any],
    tokenString: String?,
    httpMethod method: String?,
    completion: @escaping FBGraphRequestCompletion
  ) {
    let graphRequest = GraphRequest(
      graphPath: graphPath,
      parameters: parameters,
      tokenString: tokenString,
      httpMethod: method,
      flags: [.skipClientToken, .disableErrorRecovery]
    )

    graphRequest.start { _, result, error in
      completion(result, error)
    }
  }
}


/*
 * 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.
 */

final class EventDeactivationManager: _AppEventsParameterProcessing, _EventsProcessing {
  private struct DeactivatedEvent {
    let eventName: String
    let parameters: Set<String>
  }

  private enum Keys {
    static let event = "event"
    static let eventName = "_eventName"
    static let deprecatedParameter = "deprecated_param"
    static let isDeprecatedEvent = "is_deprecated_event"
  }

  private var isEventDeactivationEnabled = false
  private var deactivatedEvents = Set<String>()
  private var eventsWithDeactivatedParameters = [DeactivatedEvent]()

  var configuredDependencies: ObjectDependencies?

  var defaultDependencies: ObjectDependencies? = .init(
    serverConfigurationProvider: _ServerConfigurationManager.shared
  )

  func enable() {
    enableOnce()
  }

  private lazy var enableOnce: () -> Void = {
    guard let dependencies = try? getDependencies() else { return {} }

    if let restrictiveParams = dependencies.serverConfigurationProvider
      .cachedServerConfiguration()
      .restrictiveParams as? [String: [String: Any]] {
      updateDeactivatedEvents(restrictiveParams)
      isEventDeactivationEnabled = true
    }
    return {}
  }()

  func processEvents(_ events: NSMutableArray) {
    guard isEventDeactivationEnabled else { return }

    events.forEach { eventDictionary in
      guard let nested = eventDictionary as? [String: [String: Any]],
            let event = nested[Keys.event],
            let eventName = event[Keys.eventName] as? String
      else { return }

      if deactivatedEvents.contains(eventName) {
        events.remove(eventDictionary)
      }
    }
  }

  func processParameters(
    _ parameters: [AppEvents.ParameterName: Any]?,
    eventName: AppEvents.Name?
  ) -> [AppEvents.ParameterName: Any]? {
    guard isEventDeactivationEnabled,
          let parameters = parameters,
          !parameters.isEmpty,
          !eventsWithDeactivatedParameters.isEmpty,
          let eventName = eventName
    else {
      return parameters
    }

    let params = NSMutableDictionary(dictionary: parameters)
    parameters.keys.forEach { appEventParameterName in
      eventsWithDeactivatedParameters.forEach { event in
        if event.eventName == eventName.rawValue,
           event.parameters.contains(appEventParameterName.rawValue) {
          params.removeObject(forKey: appEventParameterName)
        }
      }
    }
    return params.copy() as? [AppEvents.ParameterName: Any] ?? parameters
  }

  private func updateDeactivatedEvents(_ events: [String: [String: Any]]) {
    guard !events.isEmpty else { return }

    deactivatedEvents.removeAll()
    eventsWithDeactivatedParameters.removeAll()

    events.forEach { event in
      if event.value.keys.contains(Keys.isDeprecatedEvent) {
        deactivatedEvents.insert(event.key)
      }

      if let deprecatedParameters = event.value[Keys.deprecatedParameter] as? [String] {
        let deactivatedEvent = DeactivatedEvent(
          eventName: event.key,
          parameters: Set(deprecatedParameters)
        )
        eventsWithDeactivatedParameters.append(deactivatedEvent)
      }
    }
  }
}

extension EventDeactivationManager: DependentAsObject {
  struct ObjectDependencies {
    var serverConfigurationProvider: _ServerConfigurationProviding
  }
}


/*
 * 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(FBSDKMACARuleMatching)
public protocol MACARuleMatching {
  @objc func enable()
  @objc func processParameters(_ params: NSDictionary?, event: String?) -> NSDictionary?
}


/*
 * 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(FBSDKMACARuleMatchingManager)
final class MACARuleMatchingManager: NSObject, MACARuleMatching {
  private var isEnable = false
  private var macaRules = [String]()

  private let keys = [
    "event",
    "_locale",
    "_appVersion",
    "_deviceOS",
    "_platform",
    "_deviceModel",
    "_nativeAppID",
    "_nativeAppShortVersion",
    "_timezone",
    "_carrier",
    "_deviceOSTypeName",
    "_deviceOSVersion",
    "_remainingDiskGB",
  ]

  var configuredDependencies: ObjectDependencies?

  var defaultDependencies: ObjectDependencies? = .init(
    serverConfigurationProvider: _ServerConfigurationManager.shared,
    deviceInformationProvider: _AppEventsDeviceInfo.shared,
    settings: Settings.shared
  )

  func enable() {
    guard !isEnable,
          let dependencies = try? getDependencies() else {
      return
    }

    if let macaRulesFromServer = dependencies.serverConfigurationProvider
      .cachedServerConfiguration()
      .protectedModeRules?["maca_rules"] as? [String] {
      macaRules = macaRulesFromServer
      isEnable = true
    }
  }

  func getKey(logic: [String: Any]) -> String? {
    logic.keys.first
  }

  // swiftlint:disable:next cyclomatic_complexity
  func stringComparison(
    variable: String,
    values: [String: String],
    data: [String: Any]
  ) -> Bool {
    // swiftlint:disable:next identifier_name
    guard let op = getKey(logic: values),
          let ruleValue = values[op],
          let dataValue = data[variable.lowercased()] ?? data[variable]
    else { return false }

    switch op {
    case "contains":
      // swiftlint:disable:next blank_line_after_single_line_guard
      guard let dataValue = stringValueOf(dataValue) else { return false }
      return dataValue.contains(ruleValue)
    case "i_contains":
      // swiftlint:disable:next blank_line_after_single_line_guard
      guard let dataValue = stringValueOf(dataValue) else { return false }
      return dataValue.lowercased().contains(ruleValue.lowercased())
    case "i_not_contains":
      // swiftlint:disable:next blank_line_after_single_line_guard
      guard let dataValue = stringValueOf(dataValue) else { return false }
      return !dataValue.lowercased().contains(ruleValue.lowercased())
    case "regex_match":
      // swiftlint:disable:next blank_line_after_single_line_guard
      guard let dataValue = stringValueOf(dataValue) else { return false }
      return dataValue.range(of: ruleValue, options: .regularExpression, range: nil, locale: nil) != nil
    case "eq":
      // swiftlint:disable:next blank_line_after_single_line_guard
      guard let dataValue = stringValueOf(dataValue) else { return false }
      return dataValue == ruleValue
    case "neq":
      // swiftlint:disable:next blank_line_after_single_line_guard
      guard let dataValue = stringValueOf(dataValue) else { return false }
      return dataValue != ruleValue
    case "lt":
      return doubleValueOf(dataValue) < doubleValueOf(ruleValue)
    case "lte":
      return doubleValueOf(dataValue) <= doubleValueOf(ruleValue)
    case "gt":
      return doubleValueOf(dataValue) > doubleValueOf(ruleValue)
    case "gte":
      return doubleValueOf(dataValue) >= doubleValueOf(ruleValue)
    default:
      return false
    }
  }

  func stringValueOf(_ value: Any?) -> String? {
    if let str = value as? String {
      return str
    } else if let num = value as? NSNumber {
      return num.stringValue
    }
    return nil
  }

  func doubleValueOf(_ value: Any?) -> Double {
    if let num = value as? NSNumber {
      return num.doubleValue
    } else if let str = value as? String,
              let doubleValue = Double(str) {
      return doubleValue
    }
    return 0
  }

  func isMatchCCRule(
    _ rule: String?,
    data: [String: Any]
  ) -> Bool {
    guard let ruleData = rule?.data(using: String.Encoding.utf8),
          let ruleJson = try? JSONSerialization.jsonObject(
            with: ruleData, options: .mutableContainers
          ) as? [String: Any],
          let thisOp = getKey(logic: ruleJson),
          let values = ruleJson[thisOp]
    else { return false }

    if thisOp == "and" {
      guard let values = values as? [Any] else { return false }

      for ent in values {
        let ruleString = try? BasicUtility.jsonString(for: ent)
        let thisRes = isMatchCCRule(ruleString, data: data)
        if !thisRes {
          return false
        }
      }
      return true
    } else if thisOp == "or" {
      guard let values = values as? [Any] else { return false }

      for ent in values {
        let ruleString = try? BasicUtility.jsonString(for: ent)
        let thisRes = isMatchCCRule(ruleString, data: data)
        if thisRes {
          return true
        }
      }
      return false
    } else if thisOp == "not" {
      let ruleString = try? BasicUtility.jsonString(for: values)
      return !isMatchCCRule(ruleString, data: data)
    } else {
      guard let values = values as? [String: String] else { return false }

      return stringComparison(variable: thisOp, values: values, data: data)
    }
  }

  func getMatchPropertyIDs(params: [String: Any]) -> String {
    guard !macaRules.isEmpty else { return "[]" }

    let res = NSMutableArray()
    for entry in macaRules {
      guard let json = try? BasicUtility.object(forJSONString: entry) as? [String: Any],
            let pid = json["id"] as? Int64,
            let rule = json["rule"] as? String
      else { continue }
      if isMatchCCRule(rule, data: params) {
        res.add(pid)
      }
    }
    let resString = try? BasicUtility.jsonString(for: res)
    return resString ?? "[]"
  }

  @objc func processParameters(_ params: NSDictionary?, event: String?) -> NSDictionary? {
    guard isEnable, let params = params, var res = params.mutableCopy() as? [String: Any]
    else { return params }

    if isEnable {
      generateInfo(params: &res, event: event)
      res["cs_maca"] = true
      res["_audiencePropertyIds"] = getMatchPropertyIDs(params: res)
      removeGeneratedInfo(params: &res)
    }

    return res as NSDictionary
  }

  func generateInfo(params: inout [String: Any], event: String?) {
    guard let dependencies = try? getDependencies() else {
      return
    }
    params["event"] = event ?? ""
    params["_locale"] = dependencies.deviceInformationProvider.language ?? ""
    params["_appVersion"] = dependencies.deviceInformationProvider.shortVersion ?? ""
    params["_deviceOS"] = "IOS"
    params["_platform"] = "mobile"
    params["_deviceModel"] = dependencies.deviceInformationProvider.machine ?? ""
    params["_nativeAppID"] = dependencies.settings.appID ?? ""
    params["_nativeAppShortVersion"] = dependencies.deviceInformationProvider.shortVersion ?? ""
    params["_timezone"] = dependencies.deviceInformationProvider.timeZoneName ?? ""
    params["_carrier"] = dependencies.deviceInformationProvider.carrierName ?? ""
    params["_deviceOSTypeName"] = "IOS"
    params["_deviceOSVersion"] = dependencies.deviceInformationProvider.sysVersion ?? ""
    params["_remainingDiskGB"] = dependencies.deviceInformationProvider.remainingDiskSpaceGB
  }

  func removeGeneratedInfo(params: inout [String: Any]) {
    for key in keys {
      params.removeValue(forKey: key)
    }
  }
}

extension MACARuleMatchingManager: DependentAsObject {
  struct ObjectDependencies {
    var serverConfigurationProvider: _ServerConfigurationProviding
    var deviceInformationProvider: _DeviceInformationProviding
    var settings: SettingsProtocol
  }
}


/*
 * 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(FBSDKProtectedModeManager)
final class ProtectedModeManager: NSObject, _AppEventsParameterProcessing {
  private var isEnabled = false
  private let standardParametersDefault: Set<String> = [
    "_currency",
    "_valueToSum",
    "fb_availability",
    "fb_body_style",
    "fb_checkin_date",
    "fb_checkout_date",
    "fb_city",
    "fb_condition_of_vehicle",
    "fb_content_category",
    "fb_content_ids",
    "fb_content_name",
    "fb_content_type",
    "fb_contents",
    "fb_country",
    "fb_currency",
    "fb_delivery_category",
    "fb_departing_arrival_date",
    "fb_departing_departure_date",
    "fb_destination_airport",
    "fb_destination_ids",
    "fb_dma_code",
    "fb_drivetrain",
    "fb_exterior_color",
    "fb_fuel_type",
    "fb_hotel_score",
    "fb_interior_color",
    "fb_lease_end_date",
    "fb_lease_start_date",
    "fb_listing_type",
    "fb_make",
    "fb_mileage.unit",
    "fb_mileage.value",
    "fb_model",
    "fb_neighborhood",
    "fb_num_adults",
    "fb_num_children",
    "fb_num_infants",
    "fb_num_items",
    "fb_order_id",
    "fb_origin_airport",
    "fb_postal_code",
    "fb_predicted_ltv",
    "fb_preferred_baths_range",
    "fb_preferred_beds_range",
    "fb_preferred_neighborhoods",
    "fb_preferred_num_stops",
    "fb_preferred_price_range",
    "fb_preferred_star_ratings",
    "fb_price",
    "fb_property_type",
    "fb_region",
    "fb_returning_arrival_date",
    "fb_returning_departure_date",
    "fb_search_string",
    "fb_state_of_vehicle",
    "fb_status",
    "fb_suggested_destinations",
    "fb_suggested_home_listings",
    "fb_suggested_hotels",
    "fb_suggested_jobs",
    "fb_suggested_local_service_businesses",
    "fb_suggested_location_based_items",
    "fb_suggested_vehicles",
    "fb_transmission",
    "fb_travel_class",
    "fb_travel_end",
    "fb_travel_start",
    "fb_trim",
    "fb_user_bucket",
    "fb_value",
    "fb_vin",
    "fb_year",
    "lead_event_source",
    "predicted_ltv",
    "product_catalog_id",
    "app_user_id",
    "appVersion",
    "_eventName",
    "_eventName_md5",
    "_currency",
    "_implicitlyLogged",
    "_inBackground",
    "_isTimedEvent",
    "_logTime",
    "fb_order_id",
    "_session_id",
    "_ui",
    "_valueToSum",
    "_valueToUpdate",
    "_is_fb_codeless",
    "_is_suggested_event",
    "_fb_pixel_referral_id",
    "fb_pixel_id",
    "trace_id",
    "user_agent",
    "subscription_id",
    "predicted_ltv",
    "event_id",
    "_restrictedParams",
    "_onDeviceParams",
    "purchase_valid_result_type",
    "core_lib_included",
    "login_lib_included",
    "share_lib_included",
    "place_lib_included",
    "messenger_lib_included",
    "applinks_lib_included",
    "marketing_lib_included",
    "_codeless_action",
    "sdk_initialized",
    "billing_client_lib_included",
    "billing_service_lib_included",
    "user_data_keys",
    "device_push_token",
    "fb_mobile_pckg_fp",
    "fb_mobile_app_cert_hash",
    "aggregate_id",
    "anonymous_id",
    "campaign_ids",
    "fb_post_attachment",
    "receipt_data",
    "ad_type",
    "fb_content",
    "fb_content_id",
    "fb_content_type",
    "fb_currency",
    "fb_description",
    "fb_level",
    "fb_max_rating_value",
    "fb_num_items",
    "fb_order_id",
    "fb_payment_info_available",
    "fb_registration_method",
    "fb_search_string",
    "fb_success",
    "pm",
    "_audiencePropertyIds",
    "cs_maca",
  ]
  private var standardParameters: Set<String> = []

  var configuredDependencies: ObjectDependencies?

  var defaultDependencies: ObjectDependencies? = .init(
    serverConfigurationProvider: _ServerConfigurationManager.shared
  )

  func enable() {
    guard let dependencies = try? getDependencies() else {
      return
    }

    if let standardParamsList = dependencies.serverConfigurationProvider
      .cachedServerConfiguration()
      .protectedModeRules?["standard_params"] as? [String] {
      if !standardParamsList.isEmpty {
        standardParameters = Set(standardParamsList)
      }
    }

    if standardParameters.isEmpty {
      standardParameters = standardParametersDefault
    }
    isEnabled = true
  }

  @objc func processParameters(
    _ parameters: [AppEvents.ParameterName: Any]?,
    eventName: AppEvents.Name?
  ) -> [AppEvents.ParameterName: Any]? {
    guard isEnabled,
          let parameters = parameters,
          !parameters.isEmpty
    else {
      return parameters
    }

    var params = parameters
    parameters.keys.forEach { appEventsParameterName in
      if !standardParameters.contains(appEventsParameterName.rawValue) {
        params.removeValue(forKey: appEventsParameterName)
      }
    }
    let pmKey = AppEvents.ParameterName(rawValue: "pm")
    params[pmKey] = true
    return params
  }
}

extension ProtectedModeManager: DependentAsObject {
  struct ObjectDependencies {
    var serverConfigurationProvider: _ServerConfigurationProviding
  }
}


/*
 * 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

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

 @warning INTERNAL - DO NOT USE
 */
@objcMembers
@objc(FBSDKRestrictiveEventFilter)
public final class _RestrictiveEventFilter: NSObject {
  public let eventName: String
  public let restrictiveParameters: [String: Any]

  @objc(initWithEventName:restrictiveParameters:)
  public init(eventName: String, restrictiveParameters: [String: Any]) {
    self.eventName = eventName
    self.restrictiveParameters = restrictiveParameters
    super.init()
  }
}


/*
 * 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

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

 @warning INTERNAL - DO NOT USE
 */
@objcMembers
@objc(FBSDKSKAdNetworkEvent)
public final class _SKAdNetworkEvent: NSObject {
  public let eventName: String?
  public var values: [String: Double]?

  private enum Keys {
    static let eventName = "event_name"
    static let values = "values"
    static let currency = "currency"
    static let amount = "amount"
  }

  @objc(initWithJSON:)
  public init?(json: [String: Any]) {

    guard let eventName = json[Keys.eventName] as? String else {
      return nil
    }
    self.eventName = eventName

    guard let valueEntries = json[Keys.values] as? [[String: Any]] else {
      return
    }
    var values = [String: Double]()
    for valueEntry in valueEntries {

      guard
        let currency = valueEntry[Keys.currency] as? String,
        let amount = valueEntry[Keys.amount] as? Double
      else {
        return nil
      }

      values[currency.uppercased()] = amount
    }

    self.values = values
  }
}