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

/// A photo for sharing.
@objcMembers
@objc(FBSDKSharePhoto)
public final class SharePhoto: NSObject, ShareMedia {

  // This property maintains a single source for the video: raw data, an asset or a URL
  private var source: Source?

  /// If the photo is resident in memory, this method supplies the data.
  public var image: UIImage? {
    get { source?.image }
    set { source = Source(newValue) }
  }

  /// URL that points to a network location or the location of the photo on disk
  public var imageURL: URL? {
    get { source?.url }
    set { source = Source(newValue) }
  }

  /// The representation of the photo in the Photos library.
  public var photoAsset: PHAsset? {
    get { source?.asset }
    set { source = Source(newValue) }
  }

  /// Specifies whether the photo represented by the receiver was generated by the user (`true`)
  /// or by the application (`false`).
  public var isUserGenerated: Bool

  /**
   The user-generated caption for the photo. Note that the 'caption' must come from
   the user, as pre-filled content is forbidden by the Platform Policies (2.3).
   */
  public var caption: String?

  /**
   Convenience method to build a new photo object with an image.
   - Parameter image If the photo is resident in memory, this method supplies the data
   - Parameter isUserGenerated Specifies whether the photo represented by the receiver was generated by the user or by the
   application
   */
  public convenience init(image: UIImage, isUserGenerated: Bool) {
    self.init(source: .image(image), isUserGenerated: isUserGenerated)
  }

  /**
   Convenience method to build a new photo object with an imageURL.
   - Parameter imageURL The URL to the photo
   - Parameter isUserGenerated Specifies whether the photo represented by the receiver was generated by the user or by the
   application

   This method should only be used when adding photo content to open graph stories.
   For example, if you're trying to share a photo from the web by itself, download the image and use
   `init(image:isUserGenerated:)` instead.
   */
  public convenience init(imageURL: URL, isUserGenerated: Bool) {
    self.init(source: .url(imageURL), isUserGenerated: isUserGenerated)
  }

  /**
   Convenience method to build a new photo object with a PHAsset.
   - Parameter photoAsset: The PHAsset that represents the photo in the Photos library.
   - Parameter isUserGenerated: Specifies whether the photo represented by the receiver was generated by the user or by
   the application
   */
  public convenience init(photoAsset: PHAsset, isUserGenerated: Bool) {
    self.init(source: .asset(photoAsset), isUserGenerated: isUserGenerated)
  }

  init(
    source: Source? = nil,
    isUserGenerated: Bool = false
  ) {
    self.source = source
    self.isUserGenerated = isUserGenerated
  }
}

// MARK: - Type Dependencies

extension SharePhoto: DependentAsType {
  struct TypeDependencies {
    var errorFactory: ErrorCreating
  }

  static var configuredDependencies: TypeDependencies?

  static var defaultDependencies: TypeDependencies? = TypeDependencies(errorFactory: _ErrorFactory())
}

// MARK: - Validation

extension SharePhoto: SharingValidatable {
  /// Asks the receiver to validate that its content or media values are valid.
  @objc(validateWithOptions:error:)
  public func validate(options bridgeOptions: ShareBridgeOptions) throws {
    switch bridgeOptions {
    case .photoImageURL:
      try validateRemoteURL()
    default:
      try validateSource()
    }
  }

  private func validateRemoteURL() throws {
    let errorFactory = try Self.getDependencies().errorFactory

    guard let url = source?.url else {
      throw errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "photo",
        value: self,
        message: "imageURL is required.",
        underlyingError: nil
      )
    }

    // A web-based URL is required
    guard !url.isFileURL else {
      throw errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "imageURL",
        value: url,
        message: "Cannot refer to a local file resource.",
        underlyingError: nil
      )
    }
  }

  private func validateSource() throws {
    let errorFactory = try Self.getDependencies().errorFactory

    switch source {
    case let .asset(asset):
      guard asset.mediaType == .image else {
        throw errorFactory.invalidArgumentError(
          domain: ShareErrorDomain,
          name: "photoAsset",
          value: asset,
          message: "Must refer to a photo or other static image.",
          underlyingError: nil
        )
      }

      // Will bridge the PHAsset.localIdentifier or will load the asset and bridge the image
      return
    case let .url(url):
      guard url.isFileURL else {
        throw errorFactory.invalidArgumentError(
          domain: ShareErrorDomain,
          name: "imageURL",
          value: url,
          message: "Must refer to a local file resource.",
          underlyingError: nil
        )
      }

      // Will load the contents of the file and bridge the image
      return
    case .image:
      // Will bridge the image
      return
    case nil:
      throw errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "photo",
        value: self,
        message: "Must have an asset, image, or imageURL value.",
        underlyingError: nil
      )
    }
  }
}

extension SharePhoto {
  // This helps us make sure that only one type of source is used
  enum Source {
    case image(UIImage)
    case url(URL)
    case asset(PHAsset)

    var image: UIImage? {
      switch self {
      case let .image(image): return image
      default: return nil
      }
    }

    var url: URL? {
      switch self {
      case let .url(url): return url
      default: return nil
      }
    }

    var asset: PHAsset? {
      switch self {
      case let .asset(asset): return asset
      default: return nil
      }
    }

    init?(_ image: UIImage?) {
      guard let image = image else { return nil }

      self = .image(image)
    }

    init?(_ url: URL?) {
      guard let url = url else { return nil }

      self = .url(url)
    }

    init?(_ asset: PHAsset?) {
      guard let asset = asset else { return nil }

      self = .asset(asset)
    }
  }
}


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

/// A model for photo content to be shared.
@objcMembers
@objc(FBSDKSharePhotoContent)
public final class SharePhotoContent: NSObject {

  /// Photos to be shared.
  public var photos = [SharePhoto]()

  /**
   URL for the content being shared.

   This URL will be checked for all link meta tags for linking in platform specific ways.  See documentation
   for App Links (https://developers.facebook.com/docs/applinks/)
   */
  public var contentURL: URL?

  /// Hashtag for the content being shared.
  public var hashtag: Hashtag?

  /**
   List of IDs for taggable people to tag with this content.

   See documentation for Taggable Friends
   (https://developers.facebook.com/docs/graph-api/reference/user/taggable_friends)
   */
  public var peopleIDs = [String]()

  /// The ID for a place to tag with this content.
  public var placeID: String?

  /// A value to be added to the referrer URL when a person follows a link from this shared content on feed.
  public var ref: String?

  /// For shares into Messenger, this pageID will be used to map the app to page and attach attribution to the share.
  public var pageID: String?

  /// A unique identifier for a share involving this content, useful for tracking purposes.
  public let shareUUID: String? = UUID().uuidString
}

// MARK: - Class Dependencies

extension SharePhotoContent: DependentAsType {
  struct TypeDependencies {
    var imageFinder: MediaLibrarySearching
    var validator: ShareValidating.Type
  }

  static var configuredDependencies: TypeDependencies?

  static let defaultDependencies: TypeDependencies? = TypeDependencies(
    imageFinder: PHImageManager.default(),
    validator: _ShareUtility.self
  )
}

// MARK: - SharingContent

extension SharePhotoContent: SharingContent {
  /**
   Adds content to an existing dictionary as key/value pairs and returns the
   updated dictionary
   @param existingParameters An immutable dictionary of existing values
   @param bridgeOptions The options for bridging
   @return A new dictionary with the modified contents
   */
  @objc(addParameters:bridgeOptions:)
  public func addParameters(
    _ existingParameters: [String: Any],
    options bridgeOptions: ShareBridgeOptions
  ) -> [String: Any] {
    guard let imageFinder = Self.imageFinder else {
      return existingParameters
    }

    var images = [UIImage]()

    photos.forEach { photo in
      if let asset = photo.photoAsset {
        if let image = try? imageFinder.fb_findImage(for: asset) {
          images.append(image)
        }
      } else if let url = photo.imageURL {
        // Load the contents of the file and bridge the image
        if url.isFileURL,
           let image = UIImage(contentsOfFile: url.path) {
          images.append(image)
        }
      } else if let image = photo.image {
        // Bridge the image
        images.append(image)
      }
    }

    var updatedParameters = existingParameters
    if !images.isEmpty {
      updatedParameters["photos"] = images
    }

    return updatedParameters
  }
}

// MARK: - SharingValidatable

extension SharePhotoContent: SharingValidatable {
  // The number of photos that can be shared at once is restricted
  private static let photosCountRange = 1 ... 6

  /// Validate that this content contains valid values
  @objc(validateWithOptions:error:)
  public func validate(options bridgeOptions: ShareBridgeOptions) throws {
    let validator = try Self.getDependencies().validator
    try validator.validateArray(
      photos,
      minCount: Self.photosCountRange.lowerBound,
      maxCount: Self.photosCountRange.upperBound,
      named: "photos"
    )

    try photos.forEach { photo in
      try photo.validate(options: bridgeOptions)
    }
  }
}


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

protocol ShareUtilityProtocol {

  static func feedShareDictionary(for content: SharingContent) -> [String: Any]?

  typealias WebPhotoContentHandler = (Bool, String, [String: Any]) -> Void

  static func buildAsyncWebPhotoContent(
    _ content: SharePhotoContent,
    completion: @escaping WebPhotoContentHandler
  )

  static func buildWebShareBridgeComponents(for content: SharingContent) -> WebShareBridgeComponents

  static func hashtagString(from hashtag: Hashtag?) -> String?

  static func bridgeParameters(
    for shareContent: SharingContent,
    options bridgeOptions: ShareBridgeOptions,
    shouldFailOnDataError: Bool
  ) -> [String: Any]

  static func getContentFlags(for shareContent: SharingContent) -> ContentFlags

  static func shareMediaContentContainsPhotosAndVideos(_ shareMediaContent: ShareMediaContent) -> Bool
}

struct WebShareBridgeComponents {
  var methodName: String
  var parameters: [String: Any]

  init(methodName: String, parameters: [String: Any]) {
    self.methodName = methodName
    self.parameters = parameters
  }
}


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

protocol ShareValidating {
  static func validateRequiredValue(_ value: Any, named name: String) throws

  static func validateArgument<Argument>(
    _ value: Argument,
    named name: String,
    in possibleValues: Set<Argument>
  ) throws

  static func validateArray(
    _ array: [Any],
    minCount: Int,
    maxCount: Int,
    named name: String
  ) throws

  static func validateNetworkURL(_ url: URL, named name: String) throws

  static func validateShareContent(
    _ shareContent: SharingContent,
    options bridgeOptions: ShareBridgeOptions
  ) throws
}


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

/// A video for sharing.
@objcMembers
@objc(FBSDKShareVideo)
public final class ShareVideo: NSObject, ShareMedia {

  // This property maintains a single source for the video: raw data, an asset or a URL
  private var source: Source? {
    didSet {
      previewPhoto = nil
    }
  }

  /// The raw video data.
  public var data: Data? {
    get { source?.data }
    set { source = Source(newValue) }
  }

  /// The representation of the video in the Photos library.
  public var videoAsset: PHAsset? {
    get { source?.asset }
    set { source = Source(newValue) }
  }

  /// The file URL to the video.
  public var videoURL: URL? {
    get { source?.url }
    set { source = Source(newValue) }
  }

  /// The photo that represents the video.
  public var previewPhoto: SharePhoto?

  /**
   Convenience method to build a new video object from raw data and an optional preview photo.
   - Parameter data: The Data object that holds the raw video data.
   - Parameter previewPhoto: The photo that represents the video.
   */
  public convenience init(
    data: Data,
    previewPhoto: SharePhoto? = nil
  ) {
    self.init(source: .data(data), previewPhoto: previewPhoto)
  }

  /**
   Convenience method to build a new video object from a PHAsset and an optional preview photo.
   - Parameter videoAsset: The PHAsset that represents the video in the Photos library.
   - Parameter previewPhoto: The photo that represents the video.
   */
  public convenience init(
    videoAsset: PHAsset,
    previewPhoto: SharePhoto? = nil
  ) {
    self.init(source: .asset(videoAsset), previewPhoto: previewPhoto)
  }

  /**
   Convenience method to build a new video object from a URL and an optional preview photo.
   - Parameter videoURL: The URL to the video.
   - Parameter previewPhoto: The photo that represents the video.
   */
  public convenience init(
    videoURL: URL,
    previewPhoto: SharePhoto? = nil
  ) {
    self.init(source: .url(videoURL), previewPhoto: previewPhoto)
  }

  init(
    source: Source? = nil,
    previewPhoto: SharePhoto? = nil
  ) {
    super.init()

    self.source = source
    self.previewPhoto = previewPhoto
  }
}

// MARK: - Type Dependencies

extension ShareVideo: DependentAsType {
  struct TypeDependencies {
    var errorFactory: ErrorCreating
  }

  static var configuredDependencies: TypeDependencies?

  static var defaultDependencies: TypeDependencies? = TypeDependencies(errorFactory: _ErrorFactory())
}

// MARK: - Validation

extension ShareVideo: SharingValidatable {
  /// Asks the receiver to validate that its content or media values are valid.
  @objc(validateWithOptions:error:)
  public func validate(options bridgeOptions: ShareBridgeOptions) throws {
    // Validate that a valid asset, data, or videoURL value has been set.
    // Don't validate the preview photo -- if it isn't valid it'll be dropped from the share.
    // A default one may be created if needed.
    switch source {
    case let .asset(asset):
      try validateVideoAsset(asset, options: bridgeOptions)
    case let .data(data):
      try validateData(data, options: bridgeOptions)
    case let .url(url):
      try validateVideoURL(url, options: bridgeOptions)
    default:
      let errorFactory = try Self.getDependencies().errorFactory
      throw errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "video",
        value: self,
        message: "Must have an asset, data, or videoURL value.",
        underlyingError: nil
      )
    }
  }

  private func validateData(_ data: Data, options bridgeOptions: ShareBridgeOptions) throws {
    guard bridgeOptions == .videoData else {
      let errorFactory = try Self.getDependencies().errorFactory
      throw errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "data",
        value: data,
        message: "Cannot share video data.",
        underlyingError: nil
      )
    }
  }

  private func validateVideoAsset(_ asset: PHAsset, options bridgeOptions: ShareBridgeOptions) throws {
    guard asset.mediaType == .video else {
      let errorFactory = try Self.getDependencies().errorFactory
      throw errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "videoAsset",
        value: videoAsset,
        message: "Must refer to a video file.",
        underlyingError: nil
      )
    }

    // Will bridge the PHAsset.localIdentifier or the legacy "assets-library" URL from AVAsset
  }

  private func validateVideoURL(_ videoURL: URL, options bridgeOptions: ShareBridgeOptions) throws {
    if videoURL.scheme?.lowercased() == "assets-library" {
      // Will bridge the legacy "assets-library" URL
      return
    } else if videoURL.isFileURL,
              bridgeOptions == .videoData {
      // Will load the contents of the file and bridge the data
      return
    }

    let errorFactory = try Self.getDependencies().errorFactory
    throw errorFactory.invalidArgumentError(
      domain: ShareErrorDomain,
      name: "videoURL",
      value: videoURL,
      message: "Must refer to an asset file.",
      underlyingError: nil
    )
  }
}

extension ShareVideo {
  // This helps us make sure that only one type of source is used
  enum Source {
    case data(Data)
    case url(URL)
    case asset(PHAsset)

    var data: Data? {
      switch self {
      case let .data(data): return data
      default: return nil
      }
    }

    var url: URL? {
      switch self {
      case let .url(url): return url
      default: return nil
      }
    }

    var asset: PHAsset? {
      switch self {
      case let .asset(asset): return asset
      default: return nil
      }
    }

    init?(_ data: Data?) {
      guard let data = data else { return nil }

      self = .data(data)
    }

    init?(_ url: URL?) {
      guard let url = url else { return nil }

      self = .url(url)
    }

    init?(_ asset: PHAsset?) {
      guard let asset = asset else { return nil }

      self = .asset(asset)
    }
  }
}


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

/// A model for video content to be shared.
@objcMembers
@objc(FBSDKShareVideoContent)
public final class ShareVideoContent: NSObject {

  /// The video to be shared
  public var video = ShareVideo()

  /**
   URL for the content being shared.

   This URL will be checked for all link meta tags for linking in platform specific ways.  See documentation
   for App Links (https://developers.facebook.com/docs/applinks/)
   */
  public var contentURL: URL?

  /// Hashtag for the content being shared.
  public var hashtag: Hashtag?

  /**
   List of IDs for taggable people to tag with this content.

   See documentation for Taggable Friends
   (https://developers.facebook.com/docs/graph-api/reference/user/taggable_friends)
   */
  public var peopleIDs = [String]()

  /// The ID for a place to tag with this content.
  public var placeID: String?

  /// A value to be added to the referrer URL when a person follows a link from this shared content on feed.
  public var ref: String?

  /// For shares into Messenger, this pageID will be used to map the app to page and attach attribution to the share.
  public var pageID: String?

  /// A unique identifier for a share involving this content, useful for tracking purposes.
  public private(set) var shareUUID: String? = UUID().uuidString
}

// MARK: - Type Dependencies

extension ShareVideoContent: DependentAsType {
  struct TypeDependencies {
    var validator: ShareValidating.Type
    var mediaLibrarySearcher: MediaLibrarySearching
  }

  static var configuredDependencies: TypeDependencies?

  static var defaultDependencies: TypeDependencies? = TypeDependencies(
    validator: _ShareUtility.self,
    mediaLibrarySearcher: PHImageManager.default()
  )
}

// MARK: - Sharing Content

extension ShareVideoContent: SharingContent {

  /**
   Adds content to an existing dictionary as key/value pairs and returns the
   updated dictionary
   @param existingParameters An immutable dictionary of existing values
   @param bridgeOptions The options for bridging
   @return A new dictionary with the modified contents
   */
  @objc(addParameters:bridgeOptions:)
  public func addParameters(
    _ existingParameters: [String: Any],
    options bridgeOptions: ShareBridgeOptions
  ) -> [String: Any] {
    var updatedParameters = existingParameters
    var videoParameters = [String: Any]()

    if let asset = video.videoAsset {
      if bridgeOptions == .videoAsset {
        // Bridge the PHAsset.localIdentifier
        videoParameters["assetIdentifier"] = asset.localIdentifier
      } else if let mediaLibrarySearcher = Self.mediaLibrarySearcher,
                let url = try? mediaLibrarySearcher.fb_getVideoURL(for: asset) {
        // Bridge the legacy "assets-library" URL from AVAsset
        videoParameters["assetURL"] = url
      }
    } else if let data = video.data {
      if bridgeOptions == .videoData {
        // Bridge the data
        videoParameters["data"] = data
      }
    } else if let url = video.videoURL {
      if url.scheme?.lowercased() == "assets-library" {
        // Bridge the legacy "assets-library" URL
        videoParameters["assetURL"] = url
      } else if url.isFileURL {
        if bridgeOptions == .videoData,
           // Load the contents of the file and bridge the data
           let data = try? Data(contentsOf: url, options: .mappedIfSafe) {
          videoParameters["data"] = data
        }
      }
    }

    if let photo = video.previewPhoto {
      videoParameters["previewPhoto"] = photo
    }

    updatedParameters["video"] = videoParameters
    return updatedParameters
  }
}

extension ShareVideoContent: SharingValidatable {
  @objc(validateWithOptions:error:)
  public func validate(options bridgeOptions: ShareBridgeOptions) throws {
    let validator = try Self.getDependencies().validator
    try validator.validateRequiredValue(video, named: "video")
    try video.validate(options: bridgeOptions)
  }
}


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

/// A base interface for content to be shared.
@objc(FBSDKSharingContent)
public protocol SharingContent: NSObjectProtocol, SharingValidatable {

  /**
   URL for the content being shared.

   This URL will be checked for all link meta tags for linking in platform specific ways.
   See documentation for App Links (https://developers.facebook.com/docs/applinks/)
   */
  var contentURL: URL? { get set }

  /// Hashtag for the content being shared.
  var hashtag: Hashtag? { get set }

  /**
   List of IDs for taggable people to tag with this content.

   See documentation for Taggable Friends
   (https://developers.facebook.com/docs/graph-api/reference/user/taggable_friends)
   */
  var peopleIDs: [String] { get set }

  /// The ID for a place to tag with this content.
  var placeID: String? { get set }

  /// A value to be added to the referrer URL when a person follows a link from this shared content on feed.
  var ref: String? { get set }

  /// For shares into Messenger, this pageID will be used to map the app to page and attach attribution to the share.
  var pageID: String? { get set }

  /// A unique identifier for a share involving this content, useful for tracking purposes.
  var shareUUID: String? { get }

  /**
   Adds content to an existing dictionary as key/value pairs and returns the
   updated dictionary
   @param existingParameters An immutable dictionary of existing values
   @param bridgeOptions The options for bridging
   @return A new dictionary with the modified contents
   */
  @objc(addParameters:bridgeOptions:)
  func addParameters(
    _ existingParameters: [String: Any],
    options bridgeOptions: ShareBridgeOptions
  ) -> [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

/// An interface for validatable content and media.
@objc(FBSDKSharingValidatable)
public protocol SharingValidatable {
  /**
   Validate that this content or media contains valid values.
   - Parameter options:  The share bridge options to use for validation.
   - Throws: If the values are not valid.
   */
  @objc(validateWithOptions:error:)
  func validate(options: ShareBridgeOptions) throws
}


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

/**
 Internal Type exposed to support dependent frameworks.
 API Subject to change or removal without warning. Do not use.

 @warning INTERNAL - DO NOT USE
 */
public enum _ShareUtility {}

extension _ShareUtility: ShareValidating {

  public static func validateRequiredValue(_ value: Any, named name: String) throws {
    let isValid: Bool
    switch value {
    case let string as String:
      isValid = !string.isEmpty
    case let array as [Any]:
      isValid = !array.isEmpty
    case let dictionary as [String: Any]:
      isValid = !dictionary.isEmpty
    default:
      return
    }

    guard isValid else {
      throw _ErrorFactory().requiredArgumentError(
        domain: ShareErrorDomain,
        name: name,
        message: nil,
        underlyingError: nil
      )
    }
  }

  public static func validateArgument<Argument>(
    _ value: Argument,
    named name: String,
    in possibleValues: Set<Argument>
  ) throws {
    guard possibleValues.contains(value) else {
      throw _ErrorFactory().invalidArgumentError(
        domain: ShareErrorDomain,
        name: name,
        value: value,
        message: nil,
        underlyingError: nil
      )
    }
  }

  static func validateArray(
    _ array: [Any],
    minCount: Int,
    maxCount: Int,
    named name: String
  ) throws {
    guard (minCount ... maxCount).contains(array.count) else {
      throw _ErrorFactory().invalidArgumentError(
        domain: ShareErrorDomain,
        name: name,
        value: array,
        message: "\(name) must have \(minCount) to \(maxCount) values",
        underlyingError: nil
      )
    }
  }

  static func validateNetworkURL(_ url: URL, named name: String) throws {
    guard InternalUtility.shared.isBrowserURL(url) else {
      throw _ErrorFactory().invalidArgumentError(
        domain: ShareErrorDomain,
        name: name,
        value: url,
        message: nil,
        underlyingError: nil
      )
    }
  }

  static func validateShareContent(
    _ shareContent: SharingContent,
    options bridgeOptions: ShareBridgeOptions = []
  ) throws {
    try validateRequiredValue(shareContent, named: "shareContent")
    try shareContent.validate(options: bridgeOptions)
  }
}

// MARK: - Methods used only by ShareKit

extension _ShareUtility: ShareUtilityProtocol {

  static func buildWebShareBridgeComponents(for content: SharingContent) -> WebShareBridgeComponents {
    var parameters = [String: Any]()

    if let linkContent = content as? ShareLinkContent,
       let url = linkContent.contentURL?.absoluteString {
      parameters["href"] = url
      parameters["quote"] = linkContent.quote
    }

    if !parameters.isEmpty {
      parameters["hashtag"] = hashtagString(from: content.hashtag)
      parameters["place"] = content.placeID
      parameters["tags"] = buildWebShareTags(peopleIDs: content.peopleIDs)
    }

    return WebShareBridgeComponents(
      methodName: ShareBridgeAPI.MethodName.share,
      parameters: parameters
    )
  }

  private static func buildWebShareTags(peopleIDs: [String]) -> String? {
    guard !peopleIDs.isEmpty else { return nil }

    return peopleIDs
      .filter { !$0.isEmpty }
      .joined(separator: ",")
  }

  static func buildAsyncWebPhotoContent(
    _ content: SharePhotoContent,
    completion: @escaping ShareUtilityProtocol.WebPhotoContentHandler
  ) {
    stageImages(for: content) { stagedURIs in
      var parameters = bridgeParameters(
        for: content,
        options: .webHashtag,
        shouldFailOnDataError: false
      )
      parameters.removeValue(forKey: "photos")

      if let json = try? BasicUtility.jsonString(
        for: stagedURIs,
        invalidObjectHandler: nil
      ) {
        parameters["media"] = json
      }

      if let tags = buildWebShareTags(peopleIDs: content.peopleIDs) {
        parameters["tags"] = tags
      } else {
        parameters.removeValue(forKey: "tags")
      }

      completion(true, ShareBridgeAPI.MethodName.share, parameters)
    }
  }

  static func feedShareDictionary(for content: SharingContent) -> [String: Any]? {
    guard let linkContent = content as? ShareLinkContent else { return nil }

    let parameters: [String: Any?] = [
      "link": linkContent.contentURL,
      "quote": linkContent.quote,
      "hashtag": hashtagString(from: linkContent.hashtag),
      "place": content.placeID,
      "tags": buildWebShareTags(peopleIDs: content.peopleIDs),
      "ref": linkContent.ref,
    ]

    return parameters.compactMapValues { $0 }
  }

  static func hashtagString(from hashtag: Hashtag?) -> String? {
    guard let hashtag = hashtag else { return nil }

    guard hashtag.isValid else {
      _Logger.singleShotLogEntry(
        .developerErrors,
        logEntry: "Invalid hashtag: '\(hashtag.stringRepresentation)'"
      )

      return nil
    }

    return hashtag.stringRepresentation
  }

  static func bridgeParameters(
    for shareContent: SharingContent,
    options bridgeOptions: ShareBridgeOptions = [],
    shouldFailOnDataError: Bool
  ) -> [String: Any] {
    var nullableParameters: [String: Any?] = [
      "shareUUID": shareContent.shareUUID,
      "tags": shareContent.peopleIDs,
      "place": shareContent.placeID,
      "ref": shareContent.ref,
      "dataFailuresFatal": shouldFailOnDataError,
    ]

    // SharingContent parameters
    if let hashtag = hashtagString(from: shareContent.hashtag),
       !hashtag.isEmpty {
      // When hashtag support was originally added, the Facebook app supported an array of hashtags.
      // This was changed to support a single hashtag; however, the mobile app still expects to receive an array.
      // When hashtag support was added to web dialogs, a single hashtag was passed as a string.
      if bridgeOptions == .webHashtag {
        nullableParameters["hashtag"] = hashtag
      } else {
        nullableParameters["hashtags"] = [hashtag]
      }
    }

    var parameters = nullableParameters.compactMapValues { $0 }

    // Media/destination-specific content parameters
    let updatedParameters = shareContent.addParameters(parameters, options: bridgeOptions)
    parameters.merge(updatedParameters) { $1 }

    return parameters
  }

  static func getContentFlags(for shareContent: SharingContent) -> ContentFlags {
    switch shareContent {
    case is ShareVideoContent:
      return ContentFlags(
        containsMedia: true,
        containsVideos: true
      )
    case let photoContent as SharePhotoContent:
      return getContentFlags(for: photoContent.photos)
    case let mediaContent as ShareMediaContent:
      return getContentFlags(for: mediaContent.media)
    default:
      return ContentFlags()
    }
  }

  static func shareMediaContentContainsPhotosAndVideos(_ shareMediaContent: ShareMediaContent) -> Bool {
    let flags = getContentFlags(for: shareMediaContent)
    return flags.containsVideos && flags.containsPhotos
  }

  private static func stageImages(for photoContent: SharePhotoContent, completion: @escaping ([String]) -> Void) {
    var stagedURIs = [String]()
    let dispatchGroup = DispatchGroup()

    photoContent.photos
      .compactMap(\.image)
      .forEach { image in
        dispatchGroup.enter()

        GraphRequest(
          graphPath: "me/staging_resources",
          parameters: ["file": image],
          httpMethod: .post
        )
        .start { _, result, _ in
          defer { dispatchGroup.leave() }

          guard
            let values = result as? [String: Any],
            let uri = values["uri"] as? String
          else { return }

          stagedURIs.append(uri)
        }
      }

    dispatchGroup.notify(queue: .main) {
      completion(stagedURIs)
    }
  }

  private static func getContentFlags(for object: Any) -> ContentFlags {
    switch object {
    case let photo as SharePhoto:
      return ContentFlags(
        containsMedia: photo.image != nil,
        containsPhotos: true
      )
    case is ShareVideo:
      return ContentFlags(
        containsMedia: true,
        containsVideos: true
      )
    case let items as [Any]:
      var runningFlags = ContentFlags()

      for item in items {
        let flags = getContentFlags(for: item)
        runningFlags |= flags

        if runningFlags.containsAllTypes {
          break
        }
      }

      return runningFlags
    default:
      return ContentFlags()
    }
  }
}


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

// This protocol represents an abstraction layer for an extension on an external API,
// so its methods must start with "fb_" to safeguard against potential conflicts with the API.
protocol MediaLibrarySearching {
  func fb_findImage(for asset: PHAsset) throws -> UIImage
  func fb_getVideoURL(for asset: PHAsset) throws -> URL
}


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

extension PHImageManager: MediaLibrarySearching {
  struct MediaLibrarySearchError: Error {
    let asset: PHAsset
  }

  func fb_findImage(for asset: PHAsset) throws -> UIImage {
    let options = PHImageRequestOptions()
    options.resizeMode = .exact
    options.deliveryMode = .highQualityFormat
    options.isSynchronous = true

    // Since this variable is captured by the request closure, we have to initialize it with something.
    var result: Result<UIImage, MediaLibrarySearchError> = .failure(.init(asset: asset))

    // Since the options specify that this is a synchronous request, we are assured that this method returns only
    // after completing the request. We can thus assume that the result has been populated when we return.
    requestImage(
      for: asset,
      targetSize: PHImageManagerMaximumSize,
      contentMode: .default,
      options: options
    ) { potentialImage, _ in
      if let image = potentialImage {
        result = .success(image)
      } else {
        result = .failure(MediaLibrarySearchError(asset: asset))
      }
    }

    return try result.get()
  }

  func fb_getVideoURL(for videoAsset: PHAsset) throws -> URL {
    let semaphore = DispatchSemaphore(value: 0)
    var url: URL?

    let options = PHVideoRequestOptions()
    options.version = .current
    options.deliveryMode = .automatic
    options.isNetworkAccessAllowed = true

    requestAVAsset(forVideo: videoAsset, options: options) { potentialAsset, _, _ in
      defer { semaphore.signal() }

      guard
        let urlAsset = potentialAsset as? AVURLAsset,
        urlAsset.url.isFileURL,
        let idTerminator = videoAsset.localIdentifier.firstIndex(of: "/")
      else { return }

      let id = videoAsset.localIdentifier.prefix(upTo: idTerminator)
      let pathExtension = urlAsset.url.pathExtension
      let path = "assets-library://asset/asset.\(pathExtension)?id=\(id)&ext=\(pathExtension)"
      url = URL(string: path)
    }

    let timeout = DispatchTime.now() + .milliseconds(500)
    _ = semaphore.wait(timeout: timeout)

    if let url = url {
      return url
    } else {
      throw MediaLibrarySearchError(asset: videoAsset)
    }
  }
}


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

/**
 The error domain for all errors from FBSDKShareKit.

 Error codes from the SDK in the range 200-299 are reserved for this domain.
 */
public let ShareErrorDomain = "com.facebook.sdk.share" // swiftlint:disable:this identifier_name

/**
 ShareError
 Error codes for ShareErrorDomain.
 */
@objc(FBSDKShareError)
public enum ShareError: Int {
  /// Reserved
  case reserved = 200

  /// The error code for errors from uploading open graph objects.
  case openGraph

  /// The error code for when a sharing dialog is not available.
  /// Use the canShare methods to check for this case before calling show.
  case dialogNotAvailable

  /// The error code for unknown errors.
  case unknown
}


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

extension AppEvents.Name {
  // MARK: Message Dialog

  static let messengerShareDialogShow = AppEvents.Name("fb_messenger_dialog_share_show")
  static let messengerShareDialogResult = AppEvents.Name("fb_messenger_dialog_share_result")

  // MARK: Send Button

  static let sendButtonImpression = AppEvents.Name("fb_send_button_impression")
  static let sendButtonDidTap = AppEvents.Name("fb_send_button_did_tap")

  // MARK: Share Button

  static let shareButtonImpression = AppEvents.Name("fb_share_button_impression")
  static let shareButtonDidTap = AppEvents.Name("fb_share_button_did_tap")

  // MARK: Share Dialog

  static let shareDialogShow = AppEvents.Name("fb_dialog_share_show")
  static let shareDialogResult = AppEvents.Name("fb_dialog_share_result")
}


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

extension AppEvents.ParameterName {
  static let errorMessage = AppEvents.ParameterName("fb_dialog_outcome_error_message")
  static let outcome = AppEvents.ParameterName("fb_dialog_outcome")
  static let shareContentType = AppEvents.ParameterName("fb_dialog_share_content_type")
  static let mode = AppEvents.ParameterName("fb_dialog_mode")
  static let shareContentPageID = AppEvents.ParameterName("fb_dialog_share_content_page_id")
  static let shareContentUUID = AppEvents.ParameterName("fb_dialog_share_content_uuid")
}

enum ShareAppEventsParameters {
  enum DialogOutcomeValue {
    static let cancelled = "Cancelled"
    static let completed = "Completed"
    static let failed = "Failed"
  }

  enum ContentTypeValue {
    static let status = "Status"
    static let photo = "Photo"
    static let video = "Video"
    static let camera = "Camera"
    static let unknown = "Unknown"
  }
}


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

protocol ShareEventLogging {
  func logInternalEvent(
    _ eventName: AppEvents.Name,
    parameters: [AppEvents.ParameterName: Any]?,
    isImplicitlyLogged: Bool,
    accessToken: AccessToken?
  )
}

extension AppEvents: ShareEventLogging {}


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

/**
 A button to send content through Messenger.

 Tapping the receiver will invoke the FBSDKShareDialog with the attached shareContent.  If the dialog cannot
 be shown, the button will be disable.
 */
@objcMembers
@objc(FBSDKSendButton)
public final class FBSendButton: FBButton, SharingButton, FBButtonImpressionLogging {
  public var dialog: MessageDialog?

  public var shareContent: SharingContent? {
    get { dialog?.shareContent }
    set {
      dialog?.shareContent = newValue
      checkImplicitlyDisabled()
    }
  }

  public var analyticsParameters: [AppEvents.ParameterName: Any]? { nil }

  public var impressionTrackingEventName: AppEvents.Name { .sendButtonImpression }

  public var impressionTrackingIdentifier: String { "send" }

  public override var isImplicitlyDisabled: Bool {
    guard let dialog = dialog else { return true }

    if !dialog.canShow {
      return true
    } else {
      do {
        try dialog.validate()
        return false
      } catch {
        return true
      }
    }
  }

  public func configureButton() {
    var title = ""
    if let internalUtility = Self.internalUtility {
      title = NSLocalizedString(
        "SendButton.Send",
        tableName: "FacebookSDK",
        bundle: internalUtility.bundleForStrings,
        value: "Send",
        comment: "The label for FBSDKSendButton"
      )
    }

    let backgroundColor = UIColor(red: 0.0, green: 132.0 / 255.0, blue: 1.0, alpha: 1.0)
    let highlightedColor = UIColor(red: 0.0, green: 111.0 / 255.0, blue: 1.0, alpha: 1.0)

    configure(
      with: MessengerIcon(),
      title: title,
      backgroundColor: backgroundColor,
      highlightedColor: highlightedColor
    )
    addTarget(
      self,
      action: #selector(Self.share),
      for: .touchUpInside
    )
    dialog = MessageDialog()
  }

  // swiftlint:disable:next private_action
  @IBAction func share() {
    logTapEvent(
      withEventName: .sendButtonDidTap,
      parameters: analyticsParameters
    )

    dialog?.show()
  }
}

extension FBSendButton: DependentAsType {
  struct TypeDependencies {
    var internalUtility: InternalUtilityProtocol
  }

  static var configuredDependencies: TypeDependencies?

  static var defaultDependencies: TypeDependencies? = TypeDependencies(internalUtility: InternalUtility.shared)
}


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

import FBSDKCoreKit
import Foundation

/**
 A button to share content.

 Tapping the receiver will invoke the FBSDKShareDialog with the attached shareContent.  If the dialog cannot
 be shown, the button will be disabled.
 */
@objcMembers
@objc(FBSDKShareButton)
public final class FBShareButton: FBButton, SharingButton {
  var dialog: ShareDialog?

  public var shareContent: SharingContent? {
    get { dialog?.shareContent }
    set {
      dialog?.shareContent = newValue
      checkImplicitlyDisabled()
    }
  }

  public var analyticsParameters: [AppEvents.ParameterName: Any]? { nil }

  public var impressionTrackingEventName: AppEvents.Name { .shareButtonImpression }

  public var impressionTrackingIdentifier: String { "share" }

  public override var isImplicitlyDisabled: Bool {
    guard let dialog = dialog else { return true }

    if !dialog.canShow {
      return true
    } else {
      do {
        try dialog.validate()
        return false
      } catch {
        return true
      }
    }
  }

  public func configureButton() {
    var title = ""
    if let bundle = Self.stringProvider?.bundleForStrings {
      title = NSLocalizedString(
        "ShareButton.Share",
        tableName: "FacebookSDK",
        bundle: bundle,
        value: "Share",
        comment: "The label for FBSDKShareButton"
      )
    }

    configure(
      with: nil,
      title: title,
      backgroundColor: nil,
      highlightedColor: nil
    )
    addTarget(
      self,
      action: #selector(Self.share),
      for: .touchUpInside
    )
    dialog = ShareDialog(viewController: nil, content: nil, delegate: nil)
  }

  // swiftlint:disable:next private_action
  @IBAction func share() {
    logTapEvent(
      withEventName: .shareButtonDidTap,
      parameters: analyticsParameters
    )

    dialog?.show()
  }
}

// MARK: - Type dependencies

extension FBShareButton: DependentAsType {
  struct TypeDependencies {
    var stringProvider: UserInterfaceStringProviding
  }

  static var configuredDependencies: TypeDependencies?

  static var defaultDependencies: TypeDependencies? = TypeDependencies(stringProvider: InternalUtility.shared)
}


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

import FBSDKCoreKit

/**
 A dialog for sharing content through Messenger.

 SUPPORTED SHARE TYPES
 - FBSDKShareLinkContent

 UNSUPPORTED SHARE TYPES (DEPRECATED AUGUST 2018)
 - FBSDKShareOpenGraphContent
 - FBSDKSharePhotoContent
 - FBSDKShareVideoContent
 - FBSDKShareMessengerOpenGraphMusicTemplateContent
 - FBSDKShareMessengerMediaTemplateContent
 - FBSDKShareMessengerGenericTemplateContent
 - Any other types that are not one of the four supported types listed above
 */
@objcMembers
@objc(FBSDKMessageDialog)
public class MessageDialog: NSObject, SharingDialog { // swiftlint:disable:this prefer_final_classes

  /// The receiver's delegate or nil if it doesn't have a delegate.
  public weak var delegate: SharingDelegate?

  /// The content to be shared.
  public var shareContent: SharingContent?

  /**
   A Boolean value that indicates whether the receiver should fail if it finds an error with the share content.

   If `false`, the sharer will still be displayed without the data that was mis-configured.  For example, an
   invalid placeID specified on the shareContent would produce a data error.
   */
  public var shouldFailOnDataError = false

  private let shareDialogConfiguration: ShareDialogConfigurationProtocol

  private static var hasCheckedCanOpenURLSchemeRegistered = false

  // This only exists so that `FBSendButton` can create a dialog without content during configuration
  override convenience init() {
    self.init(content: nil, delegate: nil)
  }

  /**
   Convenience initializer to return a Message Share Dialog with content and a delegate.
   @param content The content to be shared.
   @param delegate The receiver's delegate.
   */
  @objc(initWithContent:delegate:)
  public convenience init(content: SharingContent?, delegate: SharingDelegate?) {
    self.init(
      content: content,
      delegate: delegate,
      shareDialogConfiguration: ShareDialogConfiguration()
    )
  }

  init(
    content: SharingContent?,
    delegate: SharingDelegate?,
    shareDialogConfiguration: ShareDialogConfigurationProtocol
  ) {
    let internalUtility: InternalUtilityProtocol
    do {
      internalUtility = try Self.getDependencies().internalUtility
    } catch {
      fatalError(String(describing: error))
    }

    if !Self.hasCheckedCanOpenURLSchemeRegistered {
      Self.hasCheckedCanOpenURLSchemeRegistered = true
      internalUtility.checkRegisteredCanOpenURLScheme(URLScheme.messengerApp.rawValue)
    }

    shareContent = content
    self.delegate = delegate
    self.shareDialogConfiguration = shareDialogConfiguration

    super.init()
  }

  /**
   Convenience method to return a Message Share Dialog with content and a delegate.
   @param content The content to be shared.
   @param delegate The receiver's delegate.
   */
  @objc(dialogWithContent:delegate:)
  public static func dialog(content: SharingContent?, delegate: SharingDelegate?) -> MessageDialog {
    MessageDialog(content: content, delegate: delegate)
  }

  /**
   Convenience method to show a Message Share Dialog with content and a delegate.
   @param content The content to be shared.
   @param delegate The receiver's delegate.
   */
  @objc(showWithContent:delegate:)
  public static func show(content: SharingContent?, delegate: SharingDelegate?) -> MessageDialog {
    let dialog = MessageDialog(content: content, delegate: delegate)
    dialog.show()
    return dialog
  }

  /**
   A Boolean value that indicates whether the receiver can initiate a share.

   May return `false` if the appropriate Facebook app is not installed and is required or an access token is
   required but not available.  This method does not validate the content on the receiver, so this can be checked before
   building up the content.

   See `Sharing.validate()`
   @return `true` if the receiver can share, otherwise `false`.
   */
  public var canShow: Bool { canShowNative }

  /**
   Shows the dialog.
   @return `true` if the receiver was able to begin sharing, otherwise `false`.
   */
  @discardableResult
  public func show() -> Bool {
    guard let dependencies = try? Self.getDependencies() else { return false }

    guard canShow else {
      let error = dependencies.errorFactory.error(
        domain: ShareErrorDomain,
        code: ShareError.dialogNotAvailable.rawValue,
        userInfo: nil,
        message: "Message dialog is not available.",
        underlyingError: nil
      )

      invokeDelegateDidFail(error: error)
      return false
    }

    do {
      try validate()
    } catch {
      invokeDelegateDidFail(error: error)
      return false
    }

    var parameters = [String: Any]()

    if let content = shareContent {
      parameters = dependencies.shareUtility.bridgeParameters(
        for: content,
        options: [],
        shouldFailOnDataError: shouldFailOnDataError
      )
    }

    guard let request = dependencies.bridgeAPIRequestFactory.bridgeAPIRequest(
      with: .native,
      scheme: URLScheme.messengerApp.rawValue,
      methodName: ShareBridgeAPI.MethodName.share,
      parameters: parameters,
      userInfo: nil
    )
    else { return false }

    let shouldUseSafariViewController = shareDialogConfiguration
      .shouldUseSafariViewController(forDialogName: DialogConfigurationName.message)

    dependencies.bridgeAPIRequestOpener.open(
      request,
      useSafariViewController: shouldUseSafariViewController,
      from: nil
    ) { response in
      self.handleCompletion(
        dialogResults: response.responseParameters ?? [:],
        response: response
      )
      dependencies.internalUtility.unregisterTransientObject(self)
    }

    logDialogShow()
    dependencies.internalUtility.registerTransientObject(self)

    return true
  }

  /**
   Validates the content on the receiver.
   @return `true` if the content is valid, otherwise `false`.
   */
  public func validate() throws {
    let dependencies = try Self.getDependencies()

    guard let content = shareContent else {
      let error = dependencies.errorFactory.requiredArgumentError(
        domain: ShareErrorDomain,
        name: "shareContent",
        message: nil,
        underlyingError: nil
      )
      throw error
    }

    let isContentSupported = (content is ShareLinkContent)
      || (content is SharePhotoContent)
      || (content is ShareVideoContent)

    guard isContentSupported else {
      let type = String(describing: type(of: content))
      let message = "Message dialog does not support \(type)."
      throw dependencies.errorFactory.requiredArgumentError(
        domain: ShareErrorDomain,
        name: "shareContent",
        message: message,
        underlyingError: nil
      )
    }

    try dependencies.shareUtility.validateShareContent(content, options: [])
  }

  private var canShowNative: Bool {
    guard let internalUtility = Self.internalUtility else { return false }

    let shouldUseNativeDialog = shareDialogConfiguration.shouldUseNativeDialog(
      forDialogName: DialogConfigurationName.message
    )
    return shouldUseNativeDialog && internalUtility.isMessengerAppInstalled
  }

  func handleCompletion(dialogResults: [String: Any], response: BridgeAPIResponse) {
    let completionGesture = dialogResults[ShareBridgeAPI.CompletionGesture.key] as? String
    let isCancelGesture = (completionGesture == ShareBridgeAPI.CompletionGesture.cancelValue)
    if isCancelGesture || response.isCancelled {
      invokeDelegateDidCancel()
    } else if let error = response.error {
      invokeDelegateDidFail(error: error)
    } else {
      invokeDelegateDidComplete(results: dialogResults)
    }
  }

  private func invokeDelegateDidCancel() {
    let parameters: [AppEvents.ParameterName: Any] = [
      .outcome: ShareAppEventsParameters.DialogOutcomeValue.cancelled,
    ]

    let dependencies = try? Self.getDependencies()

    dependencies?.eventLogger.logInternalEvent(
      .messengerShareDialogResult,
      parameters: parameters,
      isImplicitlyLogged: true,
      accessToken: dependencies?.accessTokenWallet.current
    )

    delegate?.sharerDidCancel(self)
  }

  private func invokeDelegateDidComplete(results: [String: Any]) {
    let parameters: [AppEvents.ParameterName: Any] = [
      .outcome: ShareAppEventsParameters.DialogOutcomeValue.completed,
    ]

    let dependencies = try? Self.getDependencies()

    dependencies?.eventLogger.logInternalEvent(
      .messengerShareDialogResult,
      parameters: parameters,
      isImplicitlyLogged: true,
      accessToken: dependencies?.accessTokenWallet.current
    )

    delegate?.sharer(self, didCompleteWithResults: results)
  }

  private func invokeDelegateDidFail(error: Error) {
    let parameters: [AppEvents.ParameterName: Any] = [
      .outcome: ShareAppEventsParameters.DialogOutcomeValue.failed,
      .errorMessage: String(describing: error),
    ]

    let dependencies = try? Self.getDependencies()

    dependencies?.eventLogger.logInternalEvent(
      .shareDialogResult,
      parameters: parameters,
      isImplicitlyLogged: true,
      accessToken: dependencies?.accessTokenWallet.current
    )

    delegate?.sharer(self, didFailWithError: error)
  }

  private func logDialogShow() {
    let contentType: String
    switch shareContent {
    case is ShareLinkContent:
      contentType = ShareAppEventsParameters.ContentTypeValue.status
    case is SharePhotoContent:
      contentType = ShareAppEventsParameters.ContentTypeValue.photo
    case is ShareVideoContent:
      contentType = ShareAppEventsParameters.ContentTypeValue.video
    default:
      contentType = ShareAppEventsParameters.ContentTypeValue.unknown
    }

    var parameters: [AppEvents.ParameterName: Any] = [.shareContentType: contentType]

    if let uuid = shareContent?.shareUUID {
      parameters[.shareContentUUID] = uuid
    }

    if let pageID = shareContent?.pageID {
      parameters[.shareContentPageID] = pageID
    }

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

    dependencies.eventLogger.logInternalEvent(
      .shareDialogShow,
      parameters: parameters,
      isImplicitlyLogged: true,
      accessToken: dependencies.accessTokenWallet.current
    )
  }
}

extension MessageDialog: DependentAsType {
  struct TypeDependencies {
    var accessTokenWallet: _AccessTokenProviding.Type
    var bridgeAPIRequestFactory: BridgeAPIRequestCreating
    var bridgeAPIRequestOpener: BridgeAPIRequestOpening
    var errorFactory: ErrorCreating
    var eventLogger: ShareEventLogging
    var internalUtility: InternalUtilityProtocol & AppAvailabilityChecker
    var shareUtility: (ShareUtilityProtocol & ShareValidating).Type
  }

  static var defaultDependencies: TypeDependencies? = TypeDependencies(
    accessTokenWallet: AccessToken.self,
    bridgeAPIRequestFactory: ShareBridgeAPIRequestFactory(),
    bridgeAPIRequestOpener: _BridgeAPI.shared,
    errorFactory: _ErrorFactory(),
    eventLogger: AppEvents.shared,
    internalUtility: InternalUtility.shared,
    shareUtility: _ShareUtility.self
  )

  static var configuredDependencies: TypeDependencies?
}


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

import FBSDKCoreKit

@objcMembers
@objc(FBSDKMessengerIcon)
final class MessengerIcon: FBIcon {

  override func path(with size: CGSize) -> CGPath? {
    let scale = CGAffineTransform(scaleX: size.width / 61.0, y: size.height / 61.0)
    let path = CGMutablePath()

    path.move(to: .init(x: 30.001, y: 0.962), transform: scale)
    path.addCurve(
      to: .init(x: 0.014, y: 28.882),
      control1: .init(x: 13.439, y: 0.962),
      control2: .init(x: 0.014, y: 13.462),
      transform: scale
    )
    path.addCurve(
      to: .init(x: 10.046, y: 49.549),
      control1: .init(x: 0.014, y: 37.165),
      control2: .init(x: 3.892, y: 44.516),
      transform: scale
    )
    path.addLine(to: .init(x: 10.046, y: 61.176), transform: scale)
    path.addLine(to: .init(x: 19.351, y: 54.722), transform: scale)
    path.addCurve(
      to: .init(x: 30.002, y: 56.502),
      control1: .init(x: 22.662, y: 55.870),
      control2: .init(x: 26.250, y: 56.502),
      transform: scale
    )
    path.addCurve(
      to: .init(x: 59.990, y: 28.882),
      control1: .init(x: 46.565, y: 56.502),
      control2: .init(x: 59.990, y: 44.301),
      transform: scale
    )
    path.addCurve(
      to: .init(x: 30.001, y: 0.962),
      control1: .init(x: 59.989, y: 13.462),
      control2: .init(x: 46.564, y: 0.962),
      transform: scale
    )
    path.closeSubpath()

    path.move(to: .init(x: 33.159, y: 37.473), transform: scale)
    path.addLine(to: .init(x: 25.403, y: 29.484), transform: scale)
    path.addLine(to: .init(x: 10.467, y: 37.674), transform: scale)
    path.addLine(to: .init(x: 26.843, y: 20.445), transform: scale)
    path.addLine(to: .init(x: 34.599, y: 28.433), transform: scale)
    path.addLine(to: .init(x: 49.535, y: 20.244), transform: scale)
    path.addLine(to: .init(x: 33.159, y: 37.473), transform: scale)
    path.closeSubpath()

    return path
  }
}


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

/// A dialog for sharing content on Facebook.
@objcMembers
@objc(FBSDKShareDialog)
public class ShareDialog: NSObject, SharingDialog { // swiftlint:disable:this prefer_final_classes
  private struct MissingContentError: Error {}
  private struct UnknownValidationError: Error {}
  private struct BridgeRequestCreationError: Error {}

  private static let feedMethodName = "feed"

  private static var hasValidatedURLSchemeRegistration = false
  private static var temporaryDirectory = URL(
    fileURLWithPath: NSTemporaryDirectory(),
    isDirectory: true
  )

  /**
   A UIViewController from which to present the dialog.

   If not specified, the topmost view controller will be automatically determined as best as possible.
   */
  public weak var fromViewController: UIViewController?

  /**
   The mode with which to display the dialog.

   Defaults to `.automatic`, which will automatically choose the best available mode.
   */
  public var mode = Mode.automatic

  /// The receiver's delegate or nil if it doesn't have a delegate.
  public weak var delegate: SharingDelegate?

  /// The content to be shared.
  public var shareContent: SharingContent?

  /**
   A Boolean value that indicates whether the receiver should fail if it finds an error with the share content.

   If `false`, the sharer will still be displayed without the data that was misconfigured.  For example, an
   invalid `placeID` specified on the `shareContent` would produce a data error.
   */
  public var shouldFailOnDataError = false

  var webDialog: _WebDialog?
  private var temporaryFiles = [URL]()

  /**
   Convenience initializer to initialize a `ShareDialog` with a view controller, content and delegate.
   @param viewController A view controller from which to present the dialog, if appropriate.
   @param content The content to be shared.
   @param delegate The dialog's delegate.
   */
  @objc(initWithViewController:content:delegate:)
  public init(
    viewController: UIViewController?,
    content: SharingContent?,
    delegate: SharingDelegate?
  ) {
    fromViewController = viewController
    shareContent = content
    self.delegate = delegate

    super.init()
  }

  deinit {
    temporaryFiles.forEach { url in
      try? FileManager.default.removeItem(at: url)
    }
  }

  /**
   Convenience method to create a `ShareDialog` with a view controller, content and delegate.
   @param viewController A view controller from which to present the dialog, if appropriate.
   @param content The content to be shared.
   @param delegate The dialog's delegate.
   */
  @objc(dialogWithViewController:withContent:delegate:)
  public class func dialog(
    viewController: UIViewController?,
    content: SharingContent?,
    delegate: SharingDelegate?
  ) -> ShareDialog {
    ShareDialog(viewController: viewController, content: content, delegate: delegate)
  }

  /**
   Convenience method to show a `ShareDialog` with a view controller, content and delegate.
   @param viewController A view controller from which to present the dialog, if appropriate.
   @param content The content to be shared.
   @param delegate The dialog's delegate.
   */
  @discardableResult
  @objc(showFromViewController:withContent:delegate:)
  public class func show(
    viewController: UIViewController?,
    content: SharingContent?,
    delegate: SharingDelegate?
  ) -> ShareDialog {
    let dialog = ShareDialog(viewController: viewController, content: content, delegate: delegate)
    dialog.show()
    return dialog
  }
}

// MARK: - Type Dependencies

extension ShareDialog: DependentAsType {
  struct TypeDependencies {
    var internalURLOpener: ShareInternalURLOpening
    var internalUtility: InternalUtilityProtocol
    var settings: SettingsProtocol
    var shareUtility: (ShareUtilityProtocol & ShareValidating).Type
    var bridgeAPIRequestFactory: BridgeAPIRequestCreating
    var bridgeAPIRequestOpener: BridgeAPIRequestOpening
    var socialComposeViewControllerFactory: SocialComposeViewControllerFactoryProtocol
    var windowFinder: _WindowFinding
    var errorFactory: ErrorCreating
    var eventLogger: ShareEventLogging
    var mediaLibrarySearcher: MediaLibrarySearching
  }

  static var configuredDependencies: TypeDependencies?

  static var defaultDependencies: TypeDependencies? = TypeDependencies(
    internalURLOpener: ShareUIApplication.shared,
    internalUtility: InternalUtility.shared,
    settings: Settings.shared,
    shareUtility: _ShareUtility.self,
    bridgeAPIRequestFactory: ShareBridgeAPIRequestFactory(),
    bridgeAPIRequestOpener: _BridgeAPI.shared,
    socialComposeViewControllerFactory: SocialComposeViewControllerFactory(),
    windowFinder: InternalUtility.shared,
    errorFactory: _ErrorFactory(),
    eventLogger: AppEvents.shared,
    mediaLibrarySearcher: PHImageManager.default()
  )

  #if DEBUG
  static func resetDependencies() {
    configuredDependencies = nil
    hasValidatedURLSchemeRegistration = false
  }
  #endif
}

extension ShareDialog {

  private static func validateURLSchemeRegistration() throws {
    guard !hasValidatedURLSchemeRegistration else { return }

    let internalUtility = try getDependencies().internalUtility

    internalUtility.checkRegisteredCanOpenURLScheme(URLScheme.facebookAPI.rawValue)
    hasValidatedURLSchemeRegistration = true
  }

  public var canShow: Bool {
    guard shareContent != nil else {
      return canShowWithoutContent
    }

    do {
      try validate()
      return true
    } catch {
      return false
    }
  }

  private var canShowWithoutContent: Bool {
    switch mode {
    case .automatic,
         .browser,
         .feedBrowser,
         .feedWeb,
         .web:
      return true
    case .native:
      return canShowNative
    case .shareSheet:
      return canShowShareSheet
    default:
      return false
    }
  }

  @discardableResult
  public func show() -> Bool {
    guard let internalUtility = Self.internalUtility else { return false }

    do {
      try validate()

      switch mode {
      case .automatic:
        try showAutomatic()
      case .browser:
        try showBrowser()
      case .feedBrowser:
        try showFeedBrowser()
      case .feedWeb:
        try showFeedWeb()
      case .native:
        try showNative()
      case .shareSheet:
        try showShareSheet()
      case .web:
        try showWeb()
      }

      logDialogShow()
      internalUtility.registerTransientObject(self)
      return true
    } catch {
      invokeDelegateDidFail(error: error)
      return false
    }
  }

  private var shouldDefaultToShareSheet: Bool {
    if shareContent is ShareCameraEffectContent {
      return false
    } else {
      return ShareDialogConfiguration().defaultShareMode == "share_sheet"
    }
  }

  private func showAutomatic() throws {
    let defaultToShareSheet = shouldDefaultToShareSheet
    let useNativeDialog = shouldUseNativeDialog

    if defaultToShareSheet,
       doesNotThrow(try showShareSheet()) {
      return
    }

    if useNativeDialog,
       doesNotThrow(try showNative()) {
      return
    }

    if !defaultToShareSheet,
       doesNotThrow(try showShareSheet()) {
      return
    }

    if doesNotThrow(try showFeedBrowser()) {
      return
    }

    if doesNotThrow(try showFeedWeb()) {
      return
    }

    if doesNotThrow(try showBrowser()) {
      return
    }

    let showWebError: Error
    do {
      try showWeb()
      return
    } catch {
      showWebError = error
    }

    if !useNativeDialog {
      try showNative()
    } else {
      throw showWebError
    }
  }

  // This method helps us turn a chain of validation methods into a predicate
  private func doesNotThrow(_ invocation: @autoclosure () throws -> Void) -> Bool {
    do {
      try invocation()
      return true
    } catch {
      return false
    }
  }

  private var canShowNative: Bool {
    Self.internalUtility?.isFacebookAppInstalled ?? false
  }

  private var canShowShareSheet: Bool {
    Self.internalUtility?.isFacebookAppInstalled ?? false
  }

  private var canAttributeThroughShareSheet: Bool {
    do {
      try Self.validateURLSchemeRegistration()
    } catch {
      return false
    }

    var components = URLComponents()
    components.scheme = URLScheme.facebookAPI.rawValue
    components.path = "/"

    var canOpenURL = false
    if let url = components.url,
       let internalURLOpener = Self.internalURLOpener {
      canOpenURL = internalURLOpener.canOpenURL(url)
    }

    return canOpenURL || canUseFBShareSheet
  }

  private var canUseFBShareSheet: Bool {
    guard let urlOpener = Self.internalURLOpener else { return false }

    var components = URLComponents()
    components.scheme = URLScheme.facebookAPI.rawValue
    components.path = "/"

    guard let url = components.url else { return false }

    return urlOpener.canOpenURL(url)
  }

  private var contentImages: [UIImage] {
    if let photoContent = shareContent as? SharePhotoContent {
      let uniqueImages = Set(photoContent.photos.compactMap(\.image))
      return Array(uniqueImages)
    } else if let mediaContent = shareContent as? ShareMediaContent {
      return mediaContent.media.compactMap { ($0 as? SharePhoto)?.image }
    } else {
      return []
    }
  }

  private func contentVideoURL(for video: ShareVideo) -> URL? {
    if let asset = video.videoAsset {
      guard let mediaLibrarySearcher = Self.mediaLibrarySearcher else { return nil }

      return try? mediaLibrarySearcher.fb_getVideoURL(for: asset)
    } else if let data = video.data {
      let file = Self.temporaryDirectory.appendingPathComponent(UUID().uuidString)
      temporaryFiles.append(file)

      do {
        try data.write(to: file, options: .atomic)
        return file
      } catch {
        return nil
      }
    } else {
      return video.videoURL
    }
  }

  private var contentVideoURLs: [URL] {
    if let videoContent = shareContent as? ShareVideoContent,
       let url = contentVideoURL(for: videoContent.video) {
      return [url]
    } else if let mediaContent = shareContent as? ShareMediaContent {
      return mediaContent.media
        .compactMap { $0 as? ShareVideo }
        .compactMap(contentVideoURL(for:))
    } else {
      return []
    }
  }

  private var contentURLs: [URL]? {
    if let linkContent = shareContent as? ShareLinkContent,
       let url = linkContent.contentURL {
      return [url]
    } else if let photoContent = shareContent as? SharePhotoContent,
              let url = photoContent.contentURL {
      return [url]
    } else {
      return nil
    }
  }

  private func handleWebResponse(
    parameters: [String: Any]? = nil,
    error potentialError: Error? = nil,
    isCancelled: Bool
  ) {
    if let error = potentialError {
      return invokeDelegateDidFail(error: error)
    }

    let completionGesture = parameters?[ShareBridgeAPI.CompletionGesture.key] as? String
    if (completionGesture == ShareBridgeAPI.CompletionGesture.cancelValue) || isCancelled {
      invokeDelegateDidCancel()
    } else {
      // Not all web dialogs report cancellation, so assume that the share has completed
      // with no additional information
      var results = [String: Any]()

      // The web response comes back with a different payload, so we need to translate it
      if let postID = parameters?[ShareBridgeAPI.PostIDKey.webParameters] {
        results[ShareBridgeAPI.PostIDKey.results] = postID
      }

      invokeDelegateDidComplete(results: results)
    }
  }

  private func photoContentHasAtLeastOneImage(_ photoContent: SharePhotoContent) -> Bool {
    photoContent.photos.contains { $0.image != nil }
  }

  private func showBrowser() throws {
    let dependencies = try Self.getDependencies()

    guard let content = shareContent else {
      throw MissingContentError()
    }

    try validateShareContentForBrowser()

    if let photoContent = content as? SharePhotoContent,
       photoContentHasAtLeastOneImage(photoContent) {
      dependencies.shareUtility.buildAsyncWebPhotoContent(photoContent) { [self] success, methodName, parameters in
        guard
          success,
          let request = dependencies.bridgeAPIRequestFactory.bridgeAPIRequest(
            with: .web,
            scheme: URLScheme.https.rawValue,
            methodName: methodName,
            parameters: parameters,
            userInfo: nil
          )
        else { return }

        dependencies.bridgeAPIRequestOpener.open(
          request,
          useSafariViewController: shouldUseSafariViewController,
          from: fromViewController
        ) { [self] response in
          handleWebResponse(
            parameters: response.responseParameters,
            error: response.error,
            isCancelled: response.isCancelled
          )

          dependencies.internalUtility.unregisterTransientObject(self)
        }
      }
    } else {
      let components = dependencies.shareUtility.buildWebShareBridgeComponents(for: content)
      guard let request = dependencies.bridgeAPIRequestFactory.bridgeAPIRequest(
        with: .web,
        scheme: URLScheme.https.rawValue,
        methodName: components.methodName,
        parameters: components.parameters,
        userInfo: nil
      )
      else {
        throw BridgeRequestCreationError()
      }

      dependencies.bridgeAPIRequestOpener.open(
        request,
        useSafariViewController: shouldUseSafariViewController,
        from: fromViewController
      ) { [self] response in
        handleWebResponse(
          parameters: response.responseParameters,
          error: response.error,
          isCancelled: response.isCancelled
        )

        dependencies.internalUtility.unregisterTransientObject(self)
      }
    }
  }

  private func showFeedBrowser() throws {
    try validateShareContentForFeed()
    let dependencies = try Self.getDependencies()

    guard let content = shareContent else {
      throw MissingContentError()
    }

    let parameters = dependencies.shareUtility.feedShareDictionary(for: content)
    guard let request = dependencies.bridgeAPIRequestFactory.bridgeAPIRequest(
      with: .web,
      scheme: URLScheme.https.rawValue,
      methodName: Self.feedMethodName,
      parameters: parameters,
      userInfo: nil
    )
    else {
      throw BridgeRequestCreationError()
    }

    dependencies.bridgeAPIRequestOpener.open(
      request,
      useSafariViewController: shouldUseSafariViewController,
      from: fromViewController
    ) { [self] response in
      handleWebResponse(
        parameters: response.responseParameters,
        error: response.error,
        isCancelled: response.isCancelled
      )

      dependencies.internalUtility.unregisterTransientObject(self)
    }
  }

  private func showFeedWeb() throws {
    try validateShareContentForFeed()
    let dependencies = try Self.getDependencies()

    guard let content = shareContent else {
      throw MissingContentError()
    }

    let parameters = dependencies.shareUtility.feedShareDictionary(for: content)

    webDialog = _WebDialog(
      name: Self.feedMethodName,
      parameters: parameters as? [String: String]
    )
    webDialog?.delegate = self
    webDialog?.show()
  }

  private func showNative() throws {
    let dependencies = try Self.getDependencies()

    guard let content = shareContent else {
      throw MissingContentError()
    }

    guard canShowNative else {
      throw dependencies.errorFactory.error(
        domain: ShareErrorDomain,
        code: ShareError.dialogNotAvailable.rawValue,
        userInfo: nil,
        message: "Native share dialog is not available.",
        underlyingError: nil
      )
    }

    try validateShareContentForNative()

    let methodName = (content is ShareCameraEffectContent)
      ? ShareBridgeAPI.MethodName.camera
      : ShareBridgeAPI.MethodName.share

    let parameters = dependencies.shareUtility.bridgeParameters(
      for: content,
      options: [],
      shouldFailOnDataError: shouldFailOnDataError
    )

    guard let request = dependencies.bridgeAPIRequestFactory.bridgeAPIRequest(
      with: .native,
      scheme: URLScheme.facebookAPI.rawValue,
      methodName: methodName,
      parameters: parameters,
      userInfo: nil
    )
    else {
      throw BridgeRequestCreationError()
    }

    dependencies.bridgeAPIRequestOpener.open(
      request,
      useSafariViewController: shouldUseSafariViewController,
      from: fromViewController
    ) { [self] response in
      let responseError = response.error as NSError?
      if responseError?.code == CoreError.errorAppVersionUnsupported.rawValue {
        do {
          try showShareSheet()
          return
        } catch {}

        do {
          try showFeedBrowser()
          return
        } catch {}
      }

      let completionGesture = response.responseParameters?[ShareBridgeAPI.CompletionGesture.key] as? String
      let didCancel = (completionGesture == ShareBridgeAPI.CompletionGesture.cancelValue)
        || response.isCancelled

      if didCancel {
        invokeDelegateDidCancel()
      } else if let error = response.error {
        invokeDelegateDidFail(error: error)
      } else {
        var results = [String: Any]()
        if let postID = response.responseParameters?[ShareBridgeAPI.PostIDKey.results] {
          results[ShareBridgeAPI.PostIDKey.results] = postID
        }
        invokeDelegateDidComplete(results: results)
      }

      dependencies.internalUtility.unregisterTransientObject(self)
    }
  }

  private func showShareSheet() throws {
    let dependencies = try Self.getDependencies()

    guard canShowShareSheet else {
      throw dependencies.errorFactory.error(
        domain: ShareErrorDomain,
        code: ShareError.dialogNotAvailable.rawValue,
        userInfo: nil,
        message: "Share sheet is not available.",
        underlyingError: nil
      )
    }

    try validateShareContentForShareSheet()

    guard let viewController = fromViewController else {
      throw dependencies.errorFactory.requiredArgumentError(
        domain: ShareErrorDomain,
        name: "fromViewController",
        message: nil,
        underlyingError: nil
      )
    }

    let composeViewController = dependencies.socialComposeViewControllerFactory.makeSocialComposeViewController()

    if let initialText = try calculateInitialText(),
       !initialText.isEmpty {
      composeViewController.setInitialText(initialText)
    }

    contentImages.forEach { image in
      composeViewController.add(image)
    }
    contentURLs?.forEach { url in
      composeViewController.add(url)
    }
    contentVideoURLs.forEach { url in
      composeViewController.add(url)
    }

    composeViewController.completionHandler = { [self] result in
      switch result {
      case .cancelled:
        invokeDelegateDidCancel()
      case .done:
        invokeDelegateDidComplete(results: [:])
      @unknown default:
        break
      }

      DispatchQueue.main.async {
        dependencies.internalUtility.unregisterTransientObject(self)
      }
    }

    viewController.present(composeViewController, animated: true)
  }

  private func showWeb() throws {
    try validateShareContentForBrowser(options: .photoImageURL)
    let dependencies = try Self.getDependencies()

    guard let content = shareContent else {
      throw MissingContentError()
    }

    let components = dependencies.shareUtility.buildWebShareBridgeComponents(for: content)

    webDialog = _WebDialog(
      name: components.methodName,
      parameters: components.parameters as? [String: String]
    )
    webDialog?.delegate = self
    webDialog?.show()
  }

  private var shouldUseNativeDialog: Bool {
    if shareContent is ShareCameraEffectContent {
      return true
    } else {
      return ShareDialogConfiguration()
        .shouldUseNativeDialog(forDialogName: DialogConfigurationName.share)
    }
  }

  private var shouldUseSafariViewController: Bool {
    if shareContent is ShareCameraEffectContent {
      return false
    } else {
      return ShareDialogConfiguration()
        .shouldUseSafariViewController(forDialogName: DialogConfigurationName.share)
    }
  }

  public func validate() throws {
    let dependencies = try Self.getDependencies()

    guard let content = shareContent else {
      throw MissingContentError()
    }

    switch content {
    case is ShareCameraEffectContent,
         is ShareLinkContent,
         is ShareMediaContent,
         is SharePhotoContent,
         is ShareVideoContent:
      break
    default:
      throw dependencies.errorFactory.requiredArgumentError(
        domain: ShareErrorDomain,
        name: "shareContent",
        message: "Share dialog does not support \(type(of: content)).",
        underlyingError: nil
      )
    }

    try dependencies.shareUtility.validateShareContent(content, options: [])

    switch mode {
    case .automatic:
      try validateAutomaticMode()
    case .native:
      try validateShareContentForNative()
    case .shareSheet:
      try validateShareContentForShareSheet()
    case .browser:
      try validateShareContentForBrowser()
    case .web:
      try validateShareContentForBrowser(options: .photoImageURL)
    case .feedBrowser,
         .feedWeb:
      try validateShareContentForFeed()
    }
  }

  private func validateAutomaticMode() throws {
    if canShowNative {
      do {
        try validateShareContentForNative()
        return
      } catch {}
    }

    if canShowShareSheet {
      do {
        try validateShareContentForShareSheet()
        return
      } catch {}
    }

    do {
      try validateShareContentForFeed()
      return
    } catch {}

    try validateShareContentForBrowser()
  }

  private func validateShareContentForBrowser(options bridgeOptions: ShareBridgeOptions = []) throws {
    let dependencies = try Self.getDependencies()

    guard let content = shareContent else {
      throw MissingContentError()
    }

    if let linkContent = content as? ShareLinkContent,
       linkContent.contentURL == nil {
      throw dependencies.errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "shareContent",
        value: linkContent,
        message: "ShareLinkContent contentURL is required.",
        underlyingError: nil
      )
    }

    guard !(shareContent is ShareCameraEffectContent) else {
      throw dependencies.errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "shareContent",
        value: shareContent,
        message: "Camera Content must be shared in `Native` mode.",
        underlyingError: nil
      )
    }

    let flags = dependencies.shareUtility.getContentFlags(for: content)

    if flags.containsPhotos {
      guard AccessToken.current != nil else {
        throw dependencies.errorFactory.invalidArgumentError(
          domain: ShareErrorDomain,
          name: "shareContent",
          value: content,
          message: "The web share dialog needs a valid access token to stage photos.",
          underlyingError: nil
        )
      }

      if let photo = content as? SharePhotoContent {
        try photo.validate(options: bridgeOptions)
      } else {
        throw dependencies.errorFactory.invalidArgumentError(
          domain: ShareErrorDomain,
          name: "shareContent",
          value: content,
          message: "Web share dialogs cannot include photos.",
          underlyingError: nil
        )
      }
    }

    if flags.containsVideos {
      throw dependencies.errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "shareContent",
        value: content,
        message: "video sharing through the browser is not supported.",
        underlyingError: nil
      )
    }

    if flags.containsMedia,
       bridgeOptions == .photoImageURL { // a web-based URL is required
      throw dependencies.errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "shareContent",
        value: content,
        message: "Web share dialogs cannot include local media.",
        underlyingError: nil
      )
    }
  }

  private func validateShareContentForFeed() throws {
    let errorFactory = try Self.getDependencies().errorFactory

    if let linkContent = shareContent as? ShareLinkContent {
      if linkContent.contentURL == nil {
        throw errorFactory.invalidArgumentError(
          domain: ShareErrorDomain,
          name: "shareContent",
          value: linkContent,
          message: "ShareLinkContent contentURL is required.",
          underlyingError: nil
        )
      }
    } else {
      throw errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "shareContent",
        value: shareContent,
        message: "Feed share dialogs support ShareLinkContent.",
        underlyingError: nil
      )
    }
  }

  private func validateShareContentForNative() throws {
    let dependencies = try Self.getDependencies()

    guard let anyContent = shareContent else {
      throw MissingContentError()
    }

    switch anyContent {
    case let media as ShareMediaContent:
      if dependencies.shareUtility.shareMediaContentContainsPhotosAndVideos(media) {
        throw dependencies.errorFactory.invalidArgumentError(
          domain: ShareErrorDomain,
          name: "shareContent",
          value: media,
          message: "Multimedia Content is only available for mode `ShareSheet`",
          underlyingError: nil
        )
      }
    case is ShareVideoContent:
      return
    default:
      break
    }

    try anyContent.validate(options: [])
  }

  private func validateShareContentForShareSheet() throws {
    let errorFactory = try Self.getDependencies().errorFactory

    guard let anyContent = shareContent else { return }

    switch anyContent {
    case let photo as SharePhotoContent:
      guard contentImages.isEmpty else { return }

      throw errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "shareContent",
        value: photo,
        message: "Share photo content must have UIImage photos in order to share with the share sheet",
        underlyingError: nil
      )
    case let video as ShareVideoContent:
      guard canUseFBShareSheet else {
        throw UnknownValidationError()
      }

      return try video.validate(options: [])
    case let media as ShareMediaContent:
      guard canUseFBShareSheet else {
        throw UnknownValidationError()
      }

      try validateShareMediaContentAvailability(content: media)
      return try media.validate(options: [])
    case is ShareLinkContent:
      return
    default:
      throw errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "shareContent",
        value: anyContent,
        message: "Share sheet does not support \(type(of: anyContent)).",
        underlyingError: nil
      )
    }
  }

  private func validateShareMediaContentAvailability(content: ShareMediaContent) throws {
    let dependencies = try Self.getDependencies()

    if dependencies.shareUtility.shareMediaContentContainsPhotosAndVideos(content),
       mode == .shareSheet,
       !canUseFBShareSheet {
      throw dependencies.errorFactory.invalidArgumentError(
        domain: ShareErrorDomain,
        name: "shareContent",
        value: content,
        message: "Cannot use the share sheet if the share sheet is unavailable. Make sure the FB app is installed.",
        underlyingError: nil
      )
    }
  }

  private func invokeDelegateDidCancel() {
    AppEvents.shared.logInternalEvent(
      .shareDialogResult,
      parameters: [.outcome: ShareAppEventsParameters.DialogOutcomeValue.cancelled],
      isImplicitlyLogged: true,
      accessToken: .current
    )

    delegate?.sharerDidCancel(self)
  }

  private func invokeDelegateDidComplete(results: [String: Any]) {
    Self.eventLogger?.logInternalEvent(
      .shareDialogResult,
      parameters: [.outcome: ShareAppEventsParameters.DialogOutcomeValue.completed],
      isImplicitlyLogged: true,
      accessToken: .current
    )

    delegate?.sharer(self, didCompleteWithResults: results)
  }

  private func invokeDelegateDidFail(error: Error) {
    let nsError = error as NSError
    let parameters: [AppEvents.ParameterName: Any] = [
      .outcome: ShareAppEventsParameters.DialogOutcomeValue.failed,
      .errorMessage: nsError.description,
    ]

    Self.eventLogger?.logInternalEvent(
      .shareDialogResult,
      parameters: parameters,
      isImplicitlyLogged: true,
      accessToken: .current
    )

    delegate?.sharer(self, didFailWithError: error)
  }

  private func logDialogShow() {
    let contentType: String
    switch shareContent {
    case is ShareLinkContent:
      contentType = ShareAppEventsParameters.ContentTypeValue.status
    case is SharePhotoContent:
      contentType = ShareAppEventsParameters.ContentTypeValue.photo
    case is ShareVideoContent:
      contentType = ShareAppEventsParameters.ContentTypeValue.video
    case is ShareCameraEffectContent:
      contentType = ShareAppEventsParameters.ContentTypeValue.camera
    default:
      contentType = ShareAppEventsParameters.ContentTypeValue.unknown
    }

    let parameters: [AppEvents.ParameterName: Any] = [
      .mode: mode.description,
      .shareContentType: contentType,
    ]

    Self.eventLogger?.logInternalEvent(
      .shareDialogShow,
      parameters: parameters,
      isImplicitlyLogged: true,
      accessToken: .current
    )
  }

  private enum ShareExtensionParameterKeys {
    static let appID = "app_id" // application identifier string
    static let hashtags = "hashtags" // array of hashtag strings (max 1)
    static let quotes = "quotes" // array of quote strings (max 1)
  }

  private func calculateInitialText() throws -> String? {
    let dependencies = try Self.getDependencies()
    let potentialHashtag = dependencies.shareUtility.hashtagString(from: shareContent?.hashtag)
    let hashtagIsEmpty = potentialHashtag?.isEmpty ?? true

    guard canAttributeThroughShareSheet else {
      return !hashtagIsEmpty ? potentialHashtag : nil
    }

    var parameters = [String: Any]()
    if let appID = dependencies.settings.appID,
       !appID.isEmpty {
      parameters[ShareExtensionParameterKeys.appID] = appID
    }
    if let hashtag = potentialHashtag,
       !hashtag.isEmpty {
      parameters[ShareExtensionParameterKeys.hashtags] = [hashtag]
    }
    if let content = shareContent as? ShareLinkContent,
       let quote = content.quote,
       !quote.isEmpty {
      parameters[ShareExtensionParameterKeys.quotes] = [quote]
    }

    if let json = try? BasicUtility.jsonString(for: parameters, invalidObjectHandler: nil) {
      return buildShareExtensionInitialText(
        appID: dependencies.settings.appID,
        hashtag: potentialHashtag,
        jsonString: json
      )
    } else {
      return nil
    }
  }

  private func buildShareExtensionInitialText(
    appID: String?,
    hashtag: String?,
    jsonString: String?
  ) -> String? {
    var text = ""

    // Not all versions of our Share Extension supported JSON.
    // Adding this text before the JSON payload supports backward compatibility.
    if let appID = appID,
       !appID.isEmpty {
      text += "fb-app-id:\(appID)"
    }

    if let hashtag = hashtag,
       !hashtag.isEmpty {
      if !text.isEmpty {
        text += " "
      }

      text += "\(hashtag)"
    }

    if let jsonString = jsonString,
       !jsonString.isEmpty {
      text += "|\(jsonString)"
    }

    return !text.isEmpty ? text : nil
  }
}

extension ShareDialog: WebDialogDelegate {
  public func webDialog(
    _ webDialog: _WebDialog,
    didCompleteWithResults results: [String: Any]
  ) {
    guard
      webDialog === self.webDialog,
      let dependencies = try? Self.getDependencies()
    else { return }

    self.webDialog = nil

    let errorCode = (results["error_code"] as? Int) ?? 0
    if errorCode == 4201 {
      invokeDelegateDidCancel()
    } else if errorCode != 0 {
      let error = dependencies.errorFactory.error(
        domain: ShareErrorDomain,
        code: ShareError.unknown.rawValue,
        userInfo: [GraphRequestErrorGraphErrorCodeKey: errorCode],
        message: results["error_message"] as? String,
        underlyingError: nil
      )
      handleWebResponse(error: error, isCancelled: false)
    } else {
      // Not all web dialogs report cancellation, so assume that the share has completed with no additional information
      handleWebResponse(parameters: results, isCancelled: false)
    }

    dependencies.internalUtility.unregisterTransientObject(self)
  }

  public func webDialog(_ webDialog: _WebDialog, didFailWithError error: Error) {
    guard self.webDialog === webDialog else { return }

    self.webDialog = nil
    invokeDelegateDidFail(error: error)

    Self.internalUtility?.unregisterTransientObject(self)
  }

  public func webDialogDidCancel(_ webDialog: _WebDialog) {
    guard self.webDialog === webDialog else { return }

    self.webDialog = nil
    invokeDelegateDidCancel()

    Self.internalUtility?.unregisterTransientObject(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 FBSDKCoreKit
import Foundation

protocol ShareDialogConfigurationProtocol {
  func shouldUseNativeDialog(forDialogName dialogName: String) -> Bool
  func shouldUseSafariViewController(forDialogName dialogName: String) -> Bool
}

extension ShareDialogConfiguration: ShareDialogConfigurationProtocol {}


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

import Foundation

extension ShareDialog {
  /**
   Modes for the FBSDKShareDialog.

   The automatic mode will progressively check the availability of different modes and open the most
   appropriate mode for the dialog that is available.
   */
  @objc(FBSDKShareDialogMode)
  public enum Mode: UInt, CustomStringConvertible {
    /// Acts with the most appropriate mode that is available.
    case automatic

    /// Displays the dialog in the main native Facebook app.
    case native

    /// Displays the dialog in the iOS integrated share sheet.
    case shareSheet

    /// Displays the dialog in Safari.
    case browser

    /// Displays the dialog in a WKWebView within the app.
    case web

    /// Displays the feed dialog in Safari.
    case feedBrowser

    /// Displays the feed dialog in a WKWebView within the app.
    case feedWeb

    /// The string description
    public var description: String {
      switch self {
      case .automatic: return "Automatic"
      case .native: return "Native"
      case .shareSheet: return "ShareSheet"
      case .browser: return "Browser"
      case .web: return "Web"
      case .feedBrowser: return "FeedBrowser"
      case .feedWeb: return "FeedWeb"
      }
    }
  }
}


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

/**
 The common interface for components that initiate sharing.

 See ShareDialog, MessageDialog
 */
@objc(FBSDKSharing)
public protocol Sharing {

  /// The receiver's delegate or nil if it doesn't have a delegate.
  weak var delegate: SharingDelegate? { get set }

  /// The content to be shared.
  var shareContent: SharingContent? { get set }

  /**
   A boolean value that indicates whether the receiver should fail if it finds an error with the share content.

   If `false`, the sharer will still be displayed without the data that was misconfigured.  For example, an
   invalid `placeID` specified on the `shareContent` would produce a data error.
   */
  var shouldFailOnDataError: Bool { get set }

  /**
   Validates the content on the receiver.

   @throws An error if the content is invalid
   */
  @objc(validateWithError:)
  func validate() throws
}


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

/**
 The common interface for sharing buttons.

 See FBSendButton and FBShareButton
 */
@objc(FBSDKSharingButton)
public protocol SharingButton {
  /// The content to be shared.
  var shareContent: SharingContent? { get set }
}


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

/**
 A delegate for types conforming to the `Sharing` protocol.

 The delegate is notified with the results of the sharer as long as the application has permissions to
 receive the information.  For example, if the person is not signed into the containing app, the sharer may not be able
 to distinguish between completion of a share and cancellation.
 */
@objc(FBSDKSharingDelegate)
public protocol SharingDelegate {

  /**
   Sent to the delegate when sharing completes without error or cancellation.
   @param sharer The sharer that completed.
   @param results The results from the sharer.  This may be nil or empty.
   */
  @objc(sharer:didCompleteWithResults:)
  func sharer(_ sharer: Sharing, didCompleteWithResults results: [String: Any])

  /**
   Sent to the delegate when the sharer encounters an error.
   @param sharer The sharer that completed.
   @param error The error.
   */
  @objc(sharer:didFailWithError:)
  func sharer(_ sharer: Sharing, didFailWithError error: Error)

  /**
   Sent to the delegate when the sharer is cancelled.
   @param sharer The sharer that completed.
   */
  @objc(sharerDidCancel:)
  func sharerDidCancel(_ sharer: Sharing)
}


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

/// The common interface for dialogs that initiate sharing.
@objc(FBSDKSharingDialog)
public protocol SharingDialog: Sharing {

  /**
   A boolean value that indicates whether the receiver can initiate a share.

   May return `false` if the appropriate Facebook app is not installed and is required or an access token is
   required but not available.  This method does not validate the content on the receiver, so this can be checked before
   building up the content.

   See `Sharing.validate(error:)`
   @return `true` if the receiver can share, otherwise `false`.
   */
  var canShow: Bool { get }

  /**
   Shows the dialog.
   @return `true` if the receiver was able to begin sharing, otherwise `false`.
   */
  @discardableResult
  func show() -> Bool
}


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

final class SocialComposeViewControllerFactory: SocialComposeViewControllerFactoryProtocol {
  private static let socialComposeServiceType = "com.apple.social.facebook"

  func makeSocialComposeViewController() -> SLComposeViewController {
    SLComposeViewController(forServiceType: Self.socialComposeServiceType)
  }
}


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

protocol SocialComposeViewControllerFactoryProtocol {
  func makeSocialComposeViewController() -> SLComposeViewController
}


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

protocol UserInterfaceStringProviding {
  var bundleForStrings: Bundle { get }
}

extension InternalUtility: UserInterfaceStringProviding {}