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

package com.facebook.login

import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.util.Pair
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultRegistryOwner
import androidx.activity.result.contract.ActivityResultContract
import androidx.annotation.RestrictTo
import androidx.annotation.VisibleForTesting
import androidx.browser.customtabs.CustomTabsClient
import androidx.fragment.app.Fragment
import com.facebook.AccessToken
import com.facebook.AuthenticationToken
import com.facebook.CallbackManager
import com.facebook.FacebookActivity
import com.facebook.FacebookAuthorizationException
import com.facebook.FacebookCallback
import com.facebook.FacebookException
import com.facebook.FacebookSdk
import com.facebook.FacebookSdk.getApplicationContext
import com.facebook.FacebookSdk.getApplicationId
import com.facebook.FacebookSdk.getGraphApiVersion
import com.facebook.GraphResponse
import com.facebook.LoginStatusCallback
import com.facebook.Profile.Companion.fetchProfileForCurrentAccessToken
import com.facebook.Profile.Companion.setCurrentProfile
import com.facebook.appevents.AppEventsConstants
import com.facebook.internal.CallbackManagerImpl
import com.facebook.internal.CallbackManagerImpl.Companion.registerStaticCallback
import com.facebook.internal.CustomTabUtils.getChromePackage
import com.facebook.internal.FragmentWrapper
import com.facebook.internal.NativeProtocol
import com.facebook.internal.PlatformServiceClient
import com.facebook.internal.ServerProtocol
import com.facebook.internal.Utility
import com.facebook.internal.Validate.sdkInitialized
import com.facebook.login.LoginMethodHandler.Companion.getUserIDFromSignedRequest
import com.facebook.login.PKCEUtil.generateCodeChallenge
import java.lang.Exception
import java.util.Date
import java.util.UUID
import kotlin.jvm.JvmOverloads
import kotlin.jvm.Volatile

/** This class manages login and permissions for Facebook. */
open class LoginManager() {
  /** The login behavior. */
  var loginBehavior = LoginBehavior.NATIVE_WITH_FALLBACK
    private set

  /** The default audience. */
  var defaultAudience = DefaultAudience.FRIENDS
    private set
  private val sharedPreferences: SharedPreferences

  /** The authType */
  var authType = ServerProtocol.DIALOG_REREQUEST_AUTH_TYPE
    private set
  private var messengerPageId: String? = null
  private var resetMessengerState = false

  /** The login target app. */
  var loginTargetApp = LoginTargetApp.FACEBOOK
    private set

  /**
   * Determines whether we are using the cross Family of Apps login experience. True if using cross
   * Family of Apps login
   */
  var isFamilyLogin = false
    private set

  /**
   * Determines if we should skip deduplicating account during x-FoA login. True if Account
   * deduplication is opted out in Family of Apps login
   */
  var shouldSkipAccountDeduplication = false
    private set

  /**
   * Starts the login process to resolve the error defined in the response. The registered login
   * callbacks will be called on completion.
   *
   * @param activity The activity which is starting the login process.
   * @param response The response that has the error.
   */
  fun resolveError(activity: Activity, response: GraphResponse) {
    startLogin(ActivityStartActivityDelegate(activity), createLoginRequestFromResponse(response))
  }

  /**
   * Starts the login process to resolve the error defined in the response. The registered login
   * callbacks will be called on completion.
   *
   * This method is deprecated and it's better to use the method with a CallbackManager as the
   * second parameter. The new method with the CallbackManager as a parameter will use AndroidX
   * activity result APIs so you won't need to override onActivityResult method on the fragment.
   *
   * @param fragment The fragment which is starting the login process.
   * @param response The response that has the error.
   */
  @Deprecated("")
  fun resolveError(fragment: Fragment, response: GraphResponse) {
    this.resolveError(FragmentWrapper(fragment), response)
  }

  /**
   * Starts the login process to resolve the error defined in the response. The registered login
   * callbacks will be called on completion.
   *
   * @param fragment The fragment which is starting the login process.
   * @param callbackManager The callback manager which is used to register callbacks.
   * @param response The response that has the error.
   */
  fun resolveError(fragment: Fragment, callbackManager: CallbackManager, response: GraphResponse) {
    val activity: ComponentActivity? = fragment.activity
    if (activity != null) {
      this.resolveError(activity, callbackManager, response)
    } else {
      throw FacebookException("Cannot obtain activity context on the fragment $fragment")
    }
  }

  /**
   * Starts the login process to resolve the error defined in the response. The registered login
   * callbacks will be called on completion.
   *
   * @param fragment The android.app.Fragment which is starting the login process.
   * @param response The response that has the error.
   */
  fun resolveError(fragment: android.app.Fragment, response: GraphResponse) {
    this.resolveError(FragmentWrapper(fragment), response)
  }

  /**
   * Starts the login process to resolve the error defined in the response. The registered login
   * callbacks will be called on completion.
   *
   * @param fragment The fragment which is starting the login process.
   * @param response The response that has the error.
   */
  private fun resolveError(fragment: FragmentWrapper, response: GraphResponse) {
    startLogin(FragmentStartActivityDelegate(fragment), createLoginRequestFromResponse(response))
  }

  /**
   * Starts the login process to resolve the error defined in the response. The registered login
   * callbacks will be called on completion.
   *
   * @param activityResultRegistryOwner The activity result register owner. Normally it is an
   * androidx ComponentActivity.
   * @param callbackManager The callback manager from Facebook SDK.
   * @param response The response that has the error.
   */
  fun resolveError(
      activityResultRegistryOwner: ActivityResultRegistryOwner,
      callbackManager: CallbackManager,
      response: GraphResponse
  ) {
    startLogin(
        AndroidxActivityResultRegistryOwnerStartActivityDelegate(
            activityResultRegistryOwner, callbackManager),
        createLoginRequestFromResponse(response))
  }

  private fun createLoginRequestFromResponse(response: GraphResponse): LoginClient.Request {
    val failedToken = response.request.accessToken
    return createLoginRequest(failedToken?.permissions?.filterNotNull())
  }

  /**
   * Registers a login callback to the given callback manager.
   *
   * @param callbackManager The callback manager that will encapsulate the callback.
   * @param callback The login callback that will be called on login completion.
   */
  fun registerCallback(
      callbackManager: CallbackManager?,
      callback: FacebookCallback<LoginResult>?
  ) {
    if (callbackManager !is CallbackManagerImpl) {
      throw FacebookException("Unexpected CallbackManager, please use the provided Factory.")
    }
    callbackManager.registerCallback(CallbackManagerImpl.RequestCodeOffset.Login.toRequestCode()) {
        resultCode,
        data ->
      onActivityResult(resultCode, data, callback)
    }
  }

  /**
   * Unregisters a login callback to the given callback manager.
   *
   * @param callbackManager The callback manager that will encapsulate the callback.
   */
  fun unregisterCallback(callbackManager: CallbackManager?) {
    if (callbackManager !is CallbackManagerImpl) {
      throw FacebookException("Unexpected CallbackManager, " + "please use the provided Factory.")
    }
    callbackManager.unregisterCallback(CallbackManagerImpl.RequestCodeOffset.Login.toRequestCode())
  }

  @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
  @JvmOverloads
  open fun onActivityResult(
      resultCode: Int,
      data: Intent?,
      callback: FacebookCallback<LoginResult>? = null
  ): Boolean {
    var exception: FacebookException? = null
    var accessToken: AccessToken? = null
    var authenticationToken: AuthenticationToken? = null
    var code = LoginClient.Result.Code.ERROR
    var loggingExtras: Map<String, String>? = null
    var originalRequest: LoginClient.Request? = null
    var isCanceled = false
    if (data != null) {
      data.setExtrasClassLoader(LoginClient.Result::class.java.classLoader)
      val result: LoginClient.Result? = data.getParcelableExtra(LoginFragment.RESULT_KEY)
      if (result != null) {
        originalRequest = result.request
        code = result.code
        if (resultCode == Activity.RESULT_OK) {
          if (result.code == LoginClient.Result.Code.SUCCESS) {
            accessToken = result.token
            authenticationToken = result.authenticationToken
          } else {
            exception = FacebookAuthorizationException(result.errorMessage)
          }
        } else if (resultCode == Activity.RESULT_CANCELED) {
          isCanceled = true
        }
        loggingExtras = result.loggingExtras
      }
    } else if (resultCode == Activity.RESULT_CANCELED) {
      isCanceled = true
      code = LoginClient.Result.Code.CANCEL
    }
    if (exception == null && accessToken == null && !isCanceled) {
      exception = FacebookException("Unexpected call to LoginManager.onActivityResult")
    }
    val wasLoginActivityTried = true
    val context: Context? = null // Sadly, there is no way to get activity context at this point.
    logCompleteLogin(
        context, code, loggingExtras, exception, wasLoginActivityTried, originalRequest)
    finishLogin(accessToken, authenticationToken, originalRequest, exception, isCanceled, callback)
    return true
  }

  /**
   * Setter for the login behavior.
   *
   * @param loginBehavior The login behavior.
   * @return The login manager.
   */
  fun setLoginBehavior(loginBehavior: LoginBehavior): LoginManager {
    this.loginBehavior = loginBehavior
    return this
  }

  /**
   * Setter for the login target app.
   *
   * @param targetApp The login target app.
   * @return The login manager.
   */
  fun setLoginTargetApp(targetApp: LoginTargetApp): LoginManager {
    loginTargetApp = targetApp
    return this
  }

  /**
   * Setter for the default audience.
   *
   * @param defaultAudience The default audience.
   * @return The login manager.
   */
  fun setDefaultAudience(defaultAudience: DefaultAudience): LoginManager {
    this.defaultAudience = defaultAudience
    return this
  }

  /**
   * Setter for the authType
   *
   * @param authType The authType
   * @return The login manager.
   */
  fun setAuthType(authType: String): LoginManager {
    this.authType = authType
    return this
  }

  /**
   * Setter for the messengerPageId
   *
   * @param messengerPageId The messengerPageId
   * @return The login manager.
   */
  fun setMessengerPageId(messengerPageId: String?): LoginManager {
    this.messengerPageId = messengerPageId
    return this
  }

  /**
   * Setter for the resetMessengerState. For developers of the app only.
   *
   * @param resetMessengerState Whether to enable resetMessengerState
   * @return The login manager.
   */
  fun setResetMessengerState(resetMessengerState: Boolean): LoginManager {
    this.resetMessengerState = resetMessengerState
    return this
  }

  /**
   * Setter for whether we are using cross Family of Apps login
   *
   * @param isFamilyLogin Whether we are using cross Family of Apps login
   * @return The login manager.
   */
  fun setFamilyLogin(isFamilyLogin: Boolean): LoginManager {
    this.isFamilyLogin = isFamilyLogin
    return this
  }

  /**
   * Setter for whether we are skipping deduplicating account during x-FoA login.
   *
   * @param shouldSkipAccountDeduplication Whether we want to opt out account deduplication
   * experience in Family of Apps login
   * @return The login manager.
   */
  fun setShouldSkipAccountDeduplication(shouldSkipAccountDeduplication: Boolean): LoginManager {
    this.shouldSkipAccountDeduplication = shouldSkipAccountDeduplication
    return this
  }

  /** Logs out the user. */
  open fun logOut() {
    AccessToken.setCurrentAccessToken(null)
    AuthenticationToken.setCurrentAuthenticationToken(null)
    setCurrentProfile(null)
    setExpressLoginStatus(false)
  }

  /**
   * Retrieves the login status for the user. This will return an access token for the app if a user
   * is logged into the Facebook for Android app on the same device and that user had previously
   * logged into the app. If an access token was retrieved then a toast will be shown telling the
   * user that they have been logged in.
   *
   * @param context An Android context
   * @param responseCallback The callback to be called when the request completes
   */
  fun retrieveLoginStatus(context: Context, responseCallback: LoginStatusCallback) {
    retrieveLoginStatus(context, LoginStatusClient.DEFAULT_TOAST_DURATION_MS, responseCallback)
  }

  /**
   * Retrieves the login status for the user. This will return an access token for the app if a user
   * is logged into the Facebook for Android app on the same device and that user had previously
   * logged into the app. If an access token was retrieved then a toast will be shown telling the
   * user that they have been logged in.
   *
   * @param context An Android context
   * @param responseCallback The callback to be called when the request completes
   * @param toastDurationMs The duration to show the success toast in milliseconds
   */
  fun retrieveLoginStatus(
      context: Context,
      toastDurationMs: Long,
      responseCallback: LoginStatusCallback
  ) {
    retrieveLoginStatusImpl(context, responseCallback, toastDurationMs)
  }

  /**
   * Logs the user in with the requested read permissions.
   *
   * This method is deprecated and it's better to use the method with a CallbackManager as the
   * second parameter. The new method with the CallbackManager as a parameter will use AndroidX
   * activity result APIs so you won't need to override onActivityResult method on the fragment.
   *
   * @param fragment The androidx.fragment.Fragment which is starting the login process.
   * @param permissions The requested permissions.
   */
  @Deprecated("")
  fun logInWithReadPermissions(fragment: Fragment, permissions: Collection<String>) {
    logInWithReadPermissions(FragmentWrapper(fragment), permissions)
  }

  /**
   * Logs the user in with the requested read permissions.
   *
   * @param fragment The androidx.fragment.Fragment which is starting the login process.
   * @param callbackManager The callback manager which is used to register callbacks.
   * @param permissions The requested permissions.
   */
  fun logInWithReadPermissions(
      fragment: Fragment,
      callbackManager: CallbackManager,
      permissions: Collection<String>
  ) {
    val activity: ComponentActivity? = fragment.activity
    if (activity != null) {
      logInWithReadPermissions(activity, callbackManager, permissions)
    } else {
      throw FacebookException("Cannot obtain activity context on the fragment $fragment")
    }
  }

  /**
   * Logs the user in with the requested read permissions.
   *
   * @param fragment The android.app.Fragment which is starting the login process.
   * @param permissions The requested permissions.
   */
  fun logInWithReadPermissions(fragment: android.app.Fragment, permissions: Collection<String>) {
    logInWithReadPermissions(FragmentWrapper(fragment), permissions)
  }

  /**
   * Logs the user in with the requested read permissions.
   *
   * @param fragment The fragment which is starting the login process.
   * @param permissions The requested permissions.
   */
  private fun logInWithReadPermissions(fragment: FragmentWrapper, permissions: Collection<String>) {
    validateReadPermissions(permissions)
    val loginConfig = LoginConfiguration(permissions)
    logIn(fragment, loginConfig)
  }

  /**
   * Logs the user in with the requested read permissions.
   *
   * @param activity The activity which is starting the login process.
   * @param permissions The requested permissions.
   */
  fun logInWithReadPermissions(activity: Activity, permissions: Collection<String>?) {
    validateReadPermissions(permissions)
    val loginConfig = LoginConfiguration(permissions)
    logIn(activity, loginConfig)
  }

  /**
   * Logs the user in with the requested read permissions.
   *
   * @param activityResultRegistryOwner The activity result register owner. Normally it is an
   * androidx ComponentActivity.
   * @param callbackManager The callback manager from Facebook SDK.
   * @param permissions The requested permissions.
   */
  fun logInWithReadPermissions(
      activityResultRegistryOwner: ActivityResultRegistryOwner,
      callbackManager: CallbackManager,
      permissions: Collection<String>
  ) {
    validateReadPermissions(permissions)
    val loginConfig = LoginConfiguration(permissions)
    logIn(activityResultRegistryOwner, callbackManager, loginConfig)
  }

  /**
   * Logs the user in with the requested configuration.
   *
   * @param fragment The android.support.v4.app.Fragment which is starting the login process.
   * @param loginConfig The login configuration
   */
  fun logInWithConfiguration(fragment: Fragment, loginConfig: LoginConfiguration) {
    loginWithConfiguration(FragmentWrapper(fragment), loginConfig)
  }

  /**
   * Logs the user in with the requested configuration.
   *
   * @param fragment The fragment which is starting the login process.
   * @param loginConfig The login configuration.
   */
  private fun loginWithConfiguration(fragment: FragmentWrapper, loginConfig: LoginConfiguration) {
    logIn(fragment, loginConfig)
  }

  /**
   * Logs the user in with the requested configuration.
   *
   * @param activity The activity which is starting the login process.
   * @param loginConfig The login configuration
   */
  fun loginWithConfiguration(activity: Activity, loginConfig: LoginConfiguration) {
    logIn(activity, loginConfig)
  }

  /**
   * Reauthorize data access
   *
   * @param activity The activity which is starting the reauthorization process.
   */
  fun reauthorizeDataAccess(activity: Activity) {
    val loginRequest = createReauthorizeRequest()
    startLogin(ActivityStartActivityDelegate(activity), loginRequest)
  }

  /**
   * Reauthorize data access
   *
   * @param fragment The android.support.v4.app.Fragment starting the reauthorization process.
   */
  fun reauthorizeDataAccess(fragment: Fragment) {
    reauthorizeDataAccess(FragmentWrapper(fragment))
  }

  /**
   * Reauthorize data access
   *
   * @param fragment The fragment which is starting the reauthorization process.
   */
  private fun reauthorizeDataAccess(fragment: FragmentWrapper) {
    val loginRequest = createReauthorizeRequest()
    startLogin(FragmentStartActivityDelegate(fragment), loginRequest)
  }

  /**
   * Logs the user in with the requested publish permissions.
   *
   * This method is deprecated and it's better to use the method with a CallbackManager as the
   * second parameter. The new method with the CallbackManager as a parameter will use AndroidX
   * activity result APIs so you won't need to override onActivityResult method on the fragment.
   *
   * @param fragment The androidx.fragment.Fragment which is starting the login process.
   * @param permissions The requested permissions.
   */
  @Deprecated("")
  fun logInWithPublishPermissions(fragment: Fragment, permissions: Collection<String>) {
    logInWithPublishPermissions(FragmentWrapper(fragment), permissions)
  }

  /**
   * Logs the user in with the requested publish permissions.
   *
   * @param fragment The androidx.fragment.Fragment which is starting the login process.
   * @param callbackManager The callback manager which is used to register callbacks.
   * @param permissions The requested permissions.
   */
  fun logInWithPublishPermissions(
      fragment: Fragment,
      callbackManager: CallbackManager,
      permissions: Collection<String>
  ) {
    val activity: ComponentActivity? = fragment.activity
    if (activity != null) {
      logInWithPublishPermissions(activity, callbackManager, permissions)
    } else {
      throw FacebookException("Cannot obtain activity context on the fragment $fragment")
    }
  }

  /**
   * Logs the user in with the requested publish permissions.
   *
   * @param fragment The android.app.Fragment which is starting the login process.
   * @param permissions The requested permissions.
   */
  fun logInWithPublishPermissions(fragment: android.app.Fragment, permissions: Collection<String>) {
    logInWithPublishPermissions(FragmentWrapper(fragment), permissions)
  }

  /**
   * Logs the user in with the requested publish permissions.
   *
   * @param fragment The fragment which is starting the login process.
   * @param permissions The requested permissions.
   */
  private fun logInWithPublishPermissions(
      fragment: FragmentWrapper,
      permissions: Collection<String>
  ) {
    validatePublishPermissions(permissions)
    val loginConfig = LoginConfiguration(permissions)
    loginWithConfiguration(fragment, loginConfig)
  }

  /**
   * Logs the user in with the requested publish permissions.
   *
   * @param activity The activity which is starting the login process.
   * @param permissions The requested permissions.
   */
  fun logInWithPublishPermissions(activity: Activity, permissions: Collection<String>?) {
    validatePublishPermissions(permissions)
    val loginConfig = LoginConfiguration(permissions)
    loginWithConfiguration(activity, loginConfig)
  }

  /**
   * Logs the user in with the requested read permissions.
   *
   * @param activityResultRegistryOwner The activity result register owner. Normally it is an
   * androidx ComponentActivity.
   * @param callbackManager The callback manager from Facebook SDK.
   * @param permissions The requested permissions.
   */
  fun logInWithPublishPermissions(
      activityResultRegistryOwner: ActivityResultRegistryOwner,
      callbackManager: CallbackManager,
      permissions: Collection<String>
  ) {
    validatePublishPermissions(permissions)
    val loginConfig = LoginConfiguration(permissions)
    logIn(activityResultRegistryOwner, callbackManager, loginConfig)
  }

  /**
   * Logs the user in with the requested permissions.
   *
   * @param fragment The android.support.v4.app.Fragment which is starting the login process.
   * @param permissions The requested permissions.
   */
  fun logIn(fragment: Fragment, permissions: Collection<String>?) {
    logIn(FragmentWrapper(fragment), permissions)
  }

  /**
   * Logs the user in with the requested permissions.
   *
   * @param fragment The android.support.v4.app.Fragment which is starting the login process.
   * @param permissions The requested permissions.
   * @param loggerID Override the default logger ID for the request
   */
  fun logIn(fragment: Fragment, permissions: Collection<String>?, loggerID: String?) {
    logIn(FragmentWrapper(fragment), permissions, loggerID)
  }

  /**
   * Logs the user in with the requested permissions.
   *
   * @param fragment The android.app.Fragment which is starting the login process.
   * @param permissions The requested permissions.
   */
  fun logIn(fragment: android.app.Fragment, permissions: Collection<String>?) {
    logIn(FragmentWrapper(fragment), permissions)
  }

  /**
   * Logs the user in with the requested permissions.
   *
   * @param fragment The android.app.Fragment which is starting the login process.
   * @param permissions The requested permissions.
   * @param loggerID Override the default logger ID for the request
   */
  fun logIn(fragment: android.app.Fragment, permissions: Collection<String>?, loggerID: String?) {
    logIn(FragmentWrapper(fragment), permissions, loggerID)
  }

  /**
   * Logs the user in with the requested permissions.
   *
   * @param fragment The fragment which is starting the login process.
   * @param permissions The requested permissions.
   */
  fun logIn(fragment: FragmentWrapper, permissions: Collection<String>?) {
    val loginConfig = LoginConfiguration(permissions)
    logIn(fragment, loginConfig)
  }

  /**
   * Logs the user in with the requested permissions.
   *
   * @param fragment The fragment which is starting the login process.
   * @param permissions The requested permissions.
   * @param loggerID Override the default logger ID for the request
   */
  fun logIn(fragment: FragmentWrapper, permissions: Collection<String>?, loggerID: String?) {
    val loginConfig = LoginConfiguration(permissions)
    val loginRequest = createLoginRequestWithConfig(loginConfig)
    if (loggerID != null) {
      loginRequest.authId = loggerID
    }
    startLogin(FragmentStartActivityDelegate(fragment), loginRequest)
  }

  /**
   * Logs the user in with the requested permissions.
   *
   * @param activity The activity which is starting the login process.
   * @param permissions The requested permissions.
   */
  fun logIn(activity: Activity, permissions: Collection<String>?) {
    val loginConfig = LoginConfiguration(permissions)
    logIn(activity, loginConfig)
  }

  /**
   * Logs the user in with the requested login configuration.
   *
   * @param fragment The fragment which is starting the login process.
   * @param loginConfig The login config of the request
   */
  fun logIn(fragment: FragmentWrapper, loginConfig: LoginConfiguration) {
    val loginRequest = createLoginRequestWithConfig(loginConfig)
    startLogin(FragmentStartActivityDelegate(fragment), loginRequest)
  }

  /**
   * Logs the user in with the requested configuration.
   *
   * @param activity The activity which is starting the login process.
   * @param loginConfig The login config of the request
   */
  fun logIn(activity: Activity, loginConfig: LoginConfiguration) {
    if (activity is ActivityResultRegistryOwner) {
      Log.w(
          TAG,
          "You're calling logging in Facebook with an activity supports androidx activity result APIs. Please follow our document to upgrade to new APIs to avoid overriding onActivityResult().")
    }
    val loginRequest = createLoginRequestWithConfig(loginConfig)
    startLogin(ActivityStartActivityDelegate(activity), loginRequest)
  }

  /**
   * Logs the user in with the requested permissions.
   *
   * @param activity The activity which is starting the login process.
   * @param permissions The requested permissions.
   * @param loggerID Override the default logger ID for the request
   */
  fun logIn(activity: Activity, permissions: Collection<String>?, loggerID: String?) {
    val loginConfig = LoginConfiguration(permissions)
    val loginRequest = createLoginRequestWithConfig(loginConfig)
    if (loggerID != null) {
      loginRequest.authId = loggerID
    }
    startLogin(ActivityStartActivityDelegate(activity), loginRequest)
  }

  /**
   * Logs the user in with the requested configuration. This method is specialized for using
   * androidx activity results APIs.
   *
   * @param activityResultRegistryOwner The activity result register owner. Normally it is an
   * androidx ComponentActivity.
   * @param callbackManager The callback manager from Facebook SDK.
   * @param loginConfig The login config of the request.
   */
  private fun logIn(
      activityResultRegistryOwner: ActivityResultRegistryOwner,
      callbackManager: CallbackManager,
      loginConfig: LoginConfiguration
  ) {
    val loginRequest = createLoginRequestWithConfig(loginConfig)
    startLogin(
        AndroidxActivityResultRegistryOwnerStartActivityDelegate(
            activityResultRegistryOwner, callbackManager),
        loginRequest)
  }

  /**
   * Logs the user in with the requested permissions.
   *
   * @param activityResultRegistryOwner The activity result register owner. Normally it is an
   * androidx ComponentActivity.
   * @param callbackManager The callback manager from Facebook SDK.
   * @param permissions The requested permissions.
   * @param loggerID Override the default logger ID for the request
   */
  fun logIn(
      activityResultRegistryOwner: ActivityResultRegistryOwner,
      callbackManager: CallbackManager,
      permissions: Collection<String>,
      loggerID: String?
  ) {
    val loginConfig = LoginConfiguration(permissions)
    val loginRequest = createLoginRequestWithConfig(loginConfig)
    if (loggerID != null) {
      loginRequest.authId = loggerID
    }
    startLogin(
        AndroidxActivityResultRegistryOwnerStartActivityDelegate(
            activityResultRegistryOwner, callbackManager),
        loginRequest)
  }

  /**
   * Logs the user in with the requested permissions.
   *
   * @param activityResultRegistryOwner The activity result register owner. Normally it is an
   * androidx ComponentActivity.
   * @param callbackManager The callback manager from Facebook SDK.
   * @param permissions The requested permissions.
   */
  fun logIn(
      activityResultRegistryOwner: ActivityResultRegistryOwner,
      callbackManager: CallbackManager,
      permissions: Collection<String>
  ) {
    val loginConfig = LoginConfiguration(permissions)
    logIn(activityResultRegistryOwner, callbackManager, loginConfig)
  }

  /**
   * The ActivityResultContract object for login. This contract can be used as the parameter for
   * `registerForActivityResult`.
   *
   * @param callbackManager the callback manager to register login callbacks.
   * @param loggerID the logger Id for the login request.
   */
  inner class FacebookLoginActivityResultContract(
      var callbackManager: CallbackManager? = null,
      var loggerID: String? = null
  ) : ActivityResultContract<Collection<String>, CallbackManager.ActivityResultParameters>() {
    override fun createIntent(context: Context, permissions: Collection<String>): Intent {
      val loginConfig = LoginConfiguration(permissions)
      val loginRequest = createLoginRequestWithConfig(loginConfig)
      loggerID?.let { loginRequest.authId = it }
      logStartLogin(context, loginRequest)
      val intent = getFacebookActivityIntent(loginRequest)
      if (!resolveIntent(intent)) {
        val exception =
            FacebookException(
                "Log in attempt failed: FacebookActivity could not be started." +
                    " Please make sure you added FacebookActivity to the AndroidManifest.")
        logCompleteLogin(
            context, LoginClient.Result.Code.ERROR, null, exception, false, loginRequest)
        throw exception
      }
      return intent
    }

    override fun parseResult(
        resultCode: Int,
        intent: Intent?
    ): CallbackManager.ActivityResultParameters {
      this@LoginManager.onActivityResult(resultCode, intent)
      val requestCode = CallbackManagerImpl.RequestCodeOffset.Login.toRequestCode()
      callbackManager?.onActivityResult(
          requestCode,
          resultCode,
          intent,
      )
      return CallbackManager.ActivityResultParameters(requestCode, resultCode, intent)
    }
  }

  /**
   * Create the ActivityResultContract object for login. This contract can be used as the parameter
   * for `registerForActivityResult`.
   *
   * For example, in an AndroidX activity/fragment:
   * ```
   *   // onCreate
   *   val launcher = registerForActivityResult(
   *     loginManager.createLogInActivityResultContract(callbackManager)) {...}
   *   // when user click the login button
   *   launcher.launch(permissions)
   * ```
   *
   * In Jetpack Compose, we can use
   * ```
   * rememberLauncherForActivityResult(
   *   contract = loginManager.createLogInActivityResultContract(callbackManager, null),
   *   onResult = {...}
   * )
   * ```
   * to create the launcher
   *
   * @param callbackManager the callback manager to register login callbacks.
   * @param loggerID the logger Id for the login request.
   */
  @JvmOverloads
  fun createLogInActivityResultContract(
      callbackManager: CallbackManager? = null,
      loggerID: String? = null
  ): FacebookLoginActivityResultContract {
    return FacebookLoginActivityResultContract(callbackManager, loggerID)
  }

  private fun validateReadPermissions(permissions: Collection<String>?) {
    if (permissions == null) {
      return
    }
    for (permission in permissions) {
      if (isPublishPermission(permission)) {
        throw FacebookException(
            "Cannot pass a publish or manage permission ($permission) to a request for read authorization",
        )
      }
    }
  }

  private fun validatePublishPermissions(permissions: Collection<String>?) {
    if (permissions == null) {
      return
    }
    for (permission in permissions) {
      if (!isPublishPermission(permission)) {
        throw FacebookException(
            "Cannot pass a read permission ($permission) to a request for publish authorization")
      }
    }
  }

  protected open fun createLoginRequestWithConfig(
      loginConfig: LoginConfiguration
  ): LoginClient.Request {
    // init the PKCE vars (code challenge and challenge method)
    var codeChallenge: String? = null
    var codeChallengeMethod = CodeChallengeMethod.S256
    try {
      codeChallenge = generateCodeChallenge(loginConfig.codeVerifier, codeChallengeMethod)
    } catch (_ex: FacebookException) {
      // fallback to 'plain' if device cannot support S256 for some reason
      codeChallengeMethod = CodeChallengeMethod.PLAIN
      codeChallenge = loginConfig.codeVerifier
    }
    val request =
        LoginClient.Request(
            loginBehavior,
            loginConfig.permissions.toSet(),
            defaultAudience,
            authType,
            getApplicationId(),
            UUID.randomUUID().toString(),
            loginTargetApp,
            loginConfig.nonce,
            loginConfig.codeVerifier,
            codeChallenge,
            codeChallengeMethod)
    request.isRerequest = AccessToken.isCurrentAccessTokenActive()
    request.messengerPageId = messengerPageId
    request.resetMessengerState = resetMessengerState
    request.isFamilyLogin = isFamilyLogin
    request.setShouldSkipAccountDeduplication(shouldSkipAccountDeduplication)
    return request
  }

  protected open fun createLoginRequest(permissions: Collection<String>?): LoginClient.Request {
    val request =
        LoginClient.Request(
            loginBehavior,
            permissions?.toSet(),
            defaultAudience,
            authType,
            getApplicationId(),
            UUID.randomUUID().toString(),
            loginTargetApp)
    request.isRerequest = AccessToken.isCurrentAccessTokenActive()
    request.messengerPageId = messengerPageId
    request.resetMessengerState = resetMessengerState
    request.isFamilyLogin = isFamilyLogin
    request.setShouldSkipAccountDeduplication(shouldSkipAccountDeduplication)
    return request
  }

  protected open fun createReauthorizeRequest(): LoginClient.Request {
    val request =
        LoginClient.Request(
            LoginBehavior.DIALOG_ONLY,
            HashSet(),
            defaultAudience,
            "reauthorize",
            getApplicationId(),
            UUID.randomUUID().toString(),
            loginTargetApp)
    request.isFamilyLogin = isFamilyLogin
    request.setShouldSkipAccountDeduplication(shouldSkipAccountDeduplication)
    return request
  }

  @Throws(FacebookException::class)
  private fun startLogin(
      startActivityDelegate: StartActivityDelegate,
      request: LoginClient.Request
  ) {
    logStartLogin(startActivityDelegate.activityContext, request)

    // Make sure the static handler for login is registered if there isn't an explicit callback
    registerStaticCallback(CallbackManagerImpl.RequestCodeOffset.Login.toRequestCode()) {
        resultCode,
        data ->
      onActivityResult(resultCode, data)
    }
    val started = tryFacebookActivity(startActivityDelegate, request)
    if (!started) {
      val exception =
          FacebookException(
              "Log in attempt failed: FacebookActivity could not be started." +
                  " Please make sure you added FacebookActivity to the AndroidManifest.")
      val wasLoginActivityTried = false
      logCompleteLogin(
          startActivityDelegate.activityContext,
          LoginClient.Result.Code.ERROR,
          null,
          exception,
          wasLoginActivityTried,
          request)
      throw exception
    }
  }

  private fun logStartLogin(context: Context?, loginRequest: LoginClient.Request?) {
    val loginLogger = LoginLoggerHolder.getLogger(context)
    if (loginLogger != null && loginRequest != null) {
      loginLogger.logStartLogin(
          loginRequest,
          if (loginRequest.isFamilyLogin) LoginLogger.EVENT_NAME_FOA_LOGIN_START
          else LoginLogger.EVENT_NAME_LOGIN_START)
    }
  }

  private fun logCompleteLogin(
      context: Context?,
      result: LoginClient.Result.Code,
      resultExtras: Map<String, String>?,
      exception: Exception?,
      wasLoginActivityTried: Boolean,
      request: LoginClient.Request?
  ) {
    val loginLogger = LoginLoggerHolder.getLogger(context) ?: return
    if (request == null) {
      // We don't expect this to happen, but if it does, log an event for diagnostic purposes.
      loginLogger.logUnexpectedError(
          LoginLogger.EVENT_NAME_LOGIN_COMPLETE,
          "Unexpected call to logCompleteLogin with null pendingAuthorizationRequest.")
    } else {
      val pendingLoggingExtras = HashMap<String, String>()
      pendingLoggingExtras[LoginLogger.EVENT_EXTRAS_TRY_LOGIN_ACTIVITY] =
          if (wasLoginActivityTried) AppEventsConstants.EVENT_PARAM_VALUE_YES
          else AppEventsConstants.EVENT_PARAM_VALUE_NO
      loginLogger.logCompleteLogin(
          request.authId,
          pendingLoggingExtras,
          result,
          resultExtras,
          exception,
          if (request.isFamilyLogin) LoginLogger.EVENT_NAME_FOA_LOGIN_COMPLETE
          else LoginLogger.EVENT_NAME_LOGIN_COMPLETE)
    }
  }

  private fun tryFacebookActivity(
      startActivityDelegate: StartActivityDelegate,
      request: LoginClient.Request
  ): Boolean {
    val intent = getFacebookActivityIntent(request)
    if (!resolveIntent(intent)) {
      return false
    }
    try {
      startActivityDelegate.startActivityForResult(intent, LoginClient.getLoginRequestCode())
    } catch (e: ActivityNotFoundException) {
      // cannot find the correct activity so the request fails
      return false
    }
    return true
  }

  private fun resolveIntent(intent: Intent): Boolean {
    val resolveInfo = getApplicationContext().packageManager.resolveActivity(intent, 0)
    return resolveInfo != null
  }

  protected open fun getFacebookActivityIntent(request: LoginClient.Request): Intent {
    val intent = Intent()
    intent.setClass(getApplicationContext(), FacebookActivity::class.java)
    intent.action = request.loginBehavior.toString()

    // Let FacebookActivity populate extras appropriately
    val extras = Bundle()
    extras.putParcelable(LoginFragment.EXTRA_REQUEST, request)
    intent.putExtra(LoginFragment.REQUEST_KEY, extras)
    return intent
  }

  private fun finishLogin(
      newToken: AccessToken?,
      newIdToken: AuthenticationToken?,
      origRequest: LoginClient.Request?,
      exception: FacebookException?,
      isCanceled: Boolean,
      callback: FacebookCallback<LoginResult>?
  ) {
    if (newToken != null) {
      AccessToken.setCurrentAccessToken(newToken)
      fetchProfileForCurrentAccessToken()
    }
    if (newIdToken != null) {
      AuthenticationToken.setCurrentAuthenticationToken(newIdToken)
    }
    if (callback != null) {
      val loginResult =
          if (newToken != null && origRequest != null)
              computeLoginResult(origRequest, newToken, newIdToken)
          else null
      // If there are no granted permissions, the operation is treated as cancel.
      if (isCanceled || (loginResult != null && loginResult.recentlyGrantedPermissions.isEmpty())) {
        callback.onCancel()
      } else if (exception != null) {
        callback.onError(exception)
      } else if (newToken != null && loginResult != null) {
        setExpressLoginStatus(true)
        callback.onSuccess(loginResult)
      }
    }
  }

  private fun retrieveLoginStatusImpl(
      context: Context,
      responseCallback: LoginStatusCallback,
      toastDurationMs: Long
  ) {
    val applicationId = getApplicationId()
    val loggerRef = UUID.randomUUID().toString()
    val logger = LoginLogger(context ?: getApplicationContext(), applicationId)
    if (!isExpressLoginAllowed) {
      logger.logLoginStatusFailure(loggerRef)
      responseCallback.onFailure()
      return
    }
    val client =
        LoginStatusClient.newInstance(
            context,
            applicationId,
            loggerRef,
            getGraphApiVersion(),
            toastDurationMs,
            null) // TODO T99739388: replace null with actual nonce
    val callback =
        PlatformServiceClient.CompletedListener { result ->
          if (result != null) {
            val errorType = result.getString(NativeProtocol.STATUS_ERROR_TYPE)
            val errorDescription = result.getString(NativeProtocol.STATUS_ERROR_DESCRIPTION)
            if (errorType != null) {
              handleLoginStatusError(
                  errorType, errorDescription, loggerRef, logger, responseCallback)
            } else {
              val token = result.getString(NativeProtocol.EXTRA_ACCESS_TOKEN)
              val expires =
                  Utility.getBundleLongAsDate(
                      result, NativeProtocol.EXTRA_EXPIRES_SECONDS_SINCE_EPOCH, Date(0))
              val permissions = result.getStringArrayList(NativeProtocol.EXTRA_PERMISSIONS)
              val signedRequest = result.getString(NativeProtocol.RESULT_ARGS_SIGNED_REQUEST)
              val graphDomain = result.getString(NativeProtocol.RESULT_ARGS_GRAPH_DOMAIN)
              val dataAccessExpirationTime =
                  Utility.getBundleLongAsDate(
                      result, NativeProtocol.EXTRA_DATA_ACCESS_EXPIRATION_TIME, Date(0))
              var userId: String? = null
              if (!signedRequest.isNullOrEmpty()) {
                userId = getUserIDFromSignedRequest(signedRequest)
              }
              if (!token.isNullOrEmpty() &&
                  !permissions.isNullOrEmpty() &&
                  !userId.isNullOrEmpty()) {
                val accessToken =
                    AccessToken(
                        token,
                        applicationId,
                        userId,
                        permissions,
                        null,
                        null,
                        null,
                        expires,
                        null,
                        dataAccessExpirationTime,
                        graphDomain)
                AccessToken.setCurrentAccessToken(accessToken)
                fetchProfileForCurrentAccessToken()
                logger.logLoginStatusSuccess(loggerRef)
                responseCallback.onCompleted(accessToken)
              } else {
                logger.logLoginStatusFailure(loggerRef)
                responseCallback.onFailure()
              }
            }
          } else {
            logger.logLoginStatusFailure(loggerRef)
            responseCallback.onFailure()
          }
        }
    client.setCompletedListener(callback)
    logger.logLoginStatusStart(loggerRef)
    if (!client.start()) {
      logger.logLoginStatusFailure(loggerRef)
      responseCallback.onFailure()
    }
  }

  private fun setExpressLoginStatus(isExpressLoginAllowed: Boolean) {
    val editor = sharedPreferences.edit()
    editor.putBoolean(EXPRESS_LOGIN_ALLOWED, isExpressLoginAllowed)
    editor.apply()
  }

  private val isExpressLoginAllowed: Boolean
    get() = sharedPreferences.getBoolean(EXPRESS_LOGIN_ALLOWED, true)

  private class AndroidxActivityResultRegistryOwnerStartActivityDelegate
  constructor(
      private val activityResultRegistryOwner: ActivityResultRegistryOwner,
      private val callbackManager: CallbackManager
  ) : StartActivityDelegate {
    override fun startActivityForResult(intent: Intent, requestCode: Int) {
      class LauncherHolder {
        var launcher: ActivityResultLauncher<Intent>? = null
      }

      val launcherHolder = LauncherHolder()
      launcherHolder.launcher =
          activityResultRegistryOwner.activityResultRegistry.register<Intent, Pair<Int, Intent>>(
              "facebook-login",
              object : ActivityResultContract<Intent, Pair<Int, Intent>>() {
                override fun createIntent(context: Context, input: Intent): Intent = input

                override fun parseResult(resultCode: Int, intent: Intent?): Pair<Int, Intent> {
                  return Pair.create(resultCode, intent)
                }
              },
              ActivityResultCallback<Pair<Int, Intent>> { result ->
                callbackManager.onActivityResult(
                    CallbackManagerImpl.RequestCodeOffset.Login.toRequestCode(),
                    result.first,
                    result.second)
                launcherHolder.launcher?.unregister()
                launcherHolder.launcher = null
              })
      launcherHolder.launcher?.launch(intent)
    }

    override val activityContext: Activity?
      get() =
          if (activityResultRegistryOwner is Activity) {
            activityResultRegistryOwner
          } else {
            null
          }
  }

  private class ActivityStartActivityDelegate internal constructor(activity: Activity) :
      StartActivityDelegate {
    override val activityContext: Activity = activity
    override fun startActivityForResult(intent: Intent, requestCode: Int) {
      activityContext.startActivityForResult(intent, requestCode)
    }
  }

  private class FragmentStartActivityDelegate constructor(private val fragment: FragmentWrapper) :
      StartActivityDelegate {
    override fun startActivityForResult(intent: Intent, requestCode: Int) {
      fragment.startActivityForResult(intent, requestCode)
    }

    override val activityContext: Activity? = fragment.activity
  }

  private object LoginLoggerHolder {
    private var logger: LoginLogger? = null

    @Synchronized
    fun getLogger(context: Context?): LoginLogger? {
      var context = context
      context = context ?: getApplicationContext()
      if (context == null) {
        return null
      }
      if (logger == null) {
        logger = LoginLogger(context, getApplicationId())
      }
      return logger
    }
  }

  companion object {
    private const val PUBLISH_PERMISSION_PREFIX = "publish"
    private const val MANAGE_PERMISSION_PREFIX = "manage"
    private const val EXPRESS_LOGIN_ALLOWED = "express_login_allowed"
    private const val PREFERENCE_LOGIN_MANAGER = "com.facebook.loginManager"
    private val OTHER_PUBLISH_PERMISSIONS = otherPublishPermissions
    private val TAG = LoginManager::class.java.toString()

    @Volatile private lateinit var instance: LoginManager

    /**
     * Getter for the login manager.
     *
     * @return The login manager.
     */
    @JvmStatic
    open fun getInstance(): LoginManager {
      if (!this::instance.isInitialized) {
        synchronized(this) { instance = LoginManager() }
      }
      return instance
    }

    /**
     * To get login result extra data from the returned intent. This method is not intended to be
     * used by 3rd-party developers.
     *
     * @param intent the intent result from the activity call
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @JvmStatic
    fun getExtraDataFromIntent(intent: Intent?): Map<String, String>? {
      if (intent == null) {
        return null
      }
      intent.setExtrasClassLoader(LoginClient.Result::class.java.classLoader)
      val result: LoginClient.Result =
          intent.getParcelableExtra(LoginFragment.RESULT_KEY) ?: return null
      return result.extraData
    }

    /**
     * To check whether the permission is for publishing content. This method is not intended to be
     * used by 3rd-party developers.
     *
     * @param permission the permission name
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @JvmStatic
    fun isPublishPermission(permission: String?): Boolean {
      return (permission != null &&
          (permission.startsWith(PUBLISH_PERMISSION_PREFIX) ||
              permission.startsWith(MANAGE_PERMISSION_PREFIX) ||
              OTHER_PUBLISH_PERMISSIONS.contains(permission)))
    }

    private val otherPublishPermissions: Set<String>
      get() {
        return setOf("ads_management", "create_event", "rsvp_event")
      }

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    @JvmStatic
    fun computeLoginResult(
        request: LoginClient.Request,
        newToken: AccessToken,
        newIdToken: AuthenticationToken?
    ): LoginResult {
      val requestedPermissions = request.permissions
      val grantedPermissions: MutableSet<String> =
          newToken.permissions.filterNotNull().toMutableSet()

      // If it's a reauth, subset the granted permissions to just the requested permissions
      // so we don't report implicit permissions like user_profile as recently granted.
      if (request.isRerequest) {
        grantedPermissions.retainAll(requestedPermissions)
      }
      val deniedPermissions: MutableSet<String> =
          requestedPermissions.filterNotNull().toMutableSet()
      deniedPermissions.removeAll(grantedPermissions)
      return LoginResult(newToken, newIdToken, grantedPermissions, deniedPermissions)
    }

    private fun handleLoginStatusError(
        errorType: String,
        errorDescription: String?,
        loggerRef: String,
        logger: LoginLogger,
        responseCallback: LoginStatusCallback
    ) {
      val exception: Exception = FacebookException("$errorType: $errorDescription")
      logger.logLoginStatusError(loggerRef, exception)
      responseCallback.onError(exception)
    }
  }

  init {
    sdkInitialized()
    sharedPreferences =
        getApplicationContext().getSharedPreferences(PREFERENCE_LOGIN_MANAGER, Context.MODE_PRIVATE)
    if (FacebookSdk.hasCustomTabsPrefetching && getChromePackage() != null) {
      // Pre-load CCT in case they are going to be used. It's happening in background,
      // and shouldn't slow anything down. Actual CCT launch should happen much faster.
      val prefetchHelper = CustomTabPrefetchHelper()
      CustomTabsClient.bindCustomTabsService(
          getApplicationContext(), "com.android.chrome", prefetchHelper)
      CustomTabsClient.connectAndInitialize(
          getApplicationContext(), getApplicationContext().packageName)
    }
  }
}


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

package com.facebook.login

import android.content.Intent
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import android.util.Base64
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.facebook.AccessToken
import com.facebook.AccessTokenSource
import com.facebook.AuthenticationToken
import com.facebook.FacebookException
import com.facebook.FacebookSdk
import com.facebook.FacebookServiceException
import com.facebook.appevents.InternalAppEventsLogger
import com.facebook.internal.AnalyticsEvents
import com.facebook.internal.NativeProtocol
import com.facebook.internal.ServerProtocol
import com.facebook.internal.Utility.getBundleLongAsDate
import com.facebook.internal.Utility.isNullOrEmpty
import com.facebook.internal.Utility.readStringMapFromParcel
import com.facebook.internal.Utility.writeStringMapToParcel
import com.facebook.login.PKCEUtil.createCodeExchangeRequest
import java.io.UnsupportedEncodingException
import java.util.Date
import kotlin.collections.HashMap
import org.json.JSONException
import org.json.JSONObject

/**
 * This is an internal class in Facebook SDK and it should not be used directly from external code.
 */
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
abstract class LoginMethodHandler : Parcelable {
  var methodLoggingExtras: MutableMap<String?, String?>? = null
  lateinit var loginClient: LoginClient
  abstract val nameForLogging: String

  /** Main constructor. */
  constructor(loginClient: LoginClient) {
    this.loginClient = loginClient
  }

  /**
   * Constructor for restoring from a parcel. It should only be called by subclass's parcel
   * restoring constructors.
   */
  protected constructor(source: Parcel) {
    methodLoggingExtras = readStringMapFromParcel(source)?.toMutableMap()
  }

  abstract fun tryAuthorize(request: LoginClient.Request): Int
  open fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean = false

  open fun needsInternetPermission(): Boolean = false

  open fun cancel() = Unit

  @Throws(JSONException::class) open fun putChallengeParam(param: JSONObject) = Unit

  protected open fun getRedirectUrl(): String {
    return "fb" + FacebookSdk.getApplicationId() + "://authorize/"
  }

  protected open fun getClientState(authId: String): String {
    val param = JSONObject()
    try {
      param.put(LoginLogger.EVENT_PARAM_AUTH_LOGGER_ID, authId)
      param.put(LoginLogger.EVENT_PARAM_METHOD, nameForLogging)
      putChallengeParam(param)
    } catch (e: JSONException) {
      Log.w("LoginMethodHandler", "Error creating client state json: " + e.message)
    }
    return param.toString()
  }

  protected open fun addLoggingExtra(key: String?, value: Any?) {
    if (methodLoggingExtras == null) {
      methodLoggingExtras = HashMap()
    }
    methodLoggingExtras?.put(key, value?.toString())
  }

  protected open fun logWebLoginCompleted(e2e: String?) {
    val applicationId = loginClient.pendingRequest?.applicationId ?: FacebookSdk.getApplicationId()
    val logger = InternalAppEventsLogger(loginClient.activity, applicationId)
    val parameters = Bundle()
    parameters.putString(AnalyticsEvents.PARAMETER_WEB_LOGIN_E2E, e2e)
    parameters.putLong(
        AnalyticsEvents.PARAMETER_WEB_LOGIN_SWITCHBACK_TIME, System.currentTimeMillis())
    parameters.putString(AnalyticsEvents.PARAMETER_APP_ID, applicationId)
    logger.logEventImplicitly(AnalyticsEvents.EVENT_WEB_LOGIN_COMPLETE, null, parameters)
  }

  /**
   * Process the code exchange for access_token and id_token and add them to Bundle
   * @param request The original request
   * @param values The bundle values or params that received from initial (response_type = code)
   * request
   * @return true if no error occurs during the code exchange request
   */
  @Throws(FacebookException::class)
  protected open fun processCodeExchange(request: LoginClient.Request, values: Bundle): Bundle {
    // Check and make sure input Bundle is correct
    // "code" is required to execute GraphRequest to exchange for
    // access_token and authentication_token
    val code = values.getString("code")
    if (isNullOrEmpty(code)) {
      throw FacebookException("No code param found from the request")
    }

    // PKCE code exchange step for access_token and authentication_token
    val codeExchangeRequest =
        code?.let {
          createCodeExchangeRequest(it, this.getRedirectUrl(), request.codeVerifier ?: "")
        }
            ?: throw FacebookException("Failed to create code exchange request")

    val PKCEResponse = codeExchangeRequest.executeAndWait()
    PKCEResponse.error?.let { requestError ->
      throw FacebookServiceException(requestError, requestError.errorMessage)
    }

    // add result of the code exchange to result bundle
    try {
      // add AccessToken to values
      val PKCEResultJson = PKCEResponse.getJSONObject()
      val accessTokenString = PKCEResultJson?.getString(ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN)
      if (PKCEResultJson == null || isNullOrEmpty(accessTokenString)) {
        throw FacebookException("No access token found from result")
      }
      values.putString(ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN, accessTokenString)

      // add AuthenticationToken to values
      if (PKCEResultJson.has(ServerProtocol.DIALOG_PARAM_AUTHENTICATION_TOKEN)) {
        values.putString(
            ServerProtocol.DIALOG_PARAM_AUTHENTICATION_TOKEN,
            PKCEResultJson.getString(ServerProtocol.DIALOG_PARAM_AUTHENTICATION_TOKEN))
      }
    } catch (ex: JSONException) {
      throw FacebookException("Fail to process code exchange response: " + ex.message)
    }

    return values
  }

  override fun writeToParcel(dest: Parcel, flags: Int) {
    writeStringMapToParcel(dest, methodLoggingExtras)
  }

  open fun shouldKeepTrackOfMultipleIntents(): Boolean = false

  companion object {
    internal const val USER_CANCELED_LOG_IN_ERROR_MESSAGE = "User canceled log in."
    internal const val NO_SIGNED_REQUEST_ERROR_MESSAGE =
        "Authorization response does not contain the signed_request"
    internal const val NO_USER_ID_ERROR_MESSAGE = "Failed to retrieve user_id from signed_request"

    @JvmStatic
    @Throws(FacebookException::class)
    fun createAuthenticationTokenFromNativeLogin(
        bundle: Bundle,
        expectedNonce: String?
    ): AuthenticationToken? {
      val authenticationTokenString = bundle.getString(NativeProtocol.EXTRA_AUTHENTICATION_TOKEN)
      return if (authenticationTokenString == null ||
          authenticationTokenString.isEmpty() ||
          expectedNonce == null ||
          expectedNonce.isEmpty()) {
        null
      } else
          try {
            AuthenticationToken(authenticationTokenString, expectedNonce)
          } catch (_ex: Exception) {
            // any exception happens we need to bubble that to FacebookException
            throw FacebookException(_ex.message)
          }
    }

    @JvmStatic
    fun createAccessTokenFromNativeLogin(
        bundle: Bundle,
        source: AccessTokenSource?,
        applicationId: String
    ): AccessToken? {
      val expires =
          getBundleLongAsDate(bundle, NativeProtocol.EXTRA_EXPIRES_SECONDS_SINCE_EPOCH, Date(0))
      val permissions = bundle.getStringArrayList(NativeProtocol.EXTRA_PERMISSIONS)
      val token = bundle.getString(NativeProtocol.EXTRA_ACCESS_TOKEN)
      val dataAccessExpirationTime =
          getBundleLongAsDate(bundle, NativeProtocol.EXTRA_DATA_ACCESS_EXPIRATION_TIME, Date(0))
      if (token == null || token.isEmpty()) {
        return null
      }
      val userId = bundle.getString(NativeProtocol.EXTRA_USER_ID)
      if (userId == null || userId.isEmpty()) {
        return null
      }
      val graphDomain = bundle.getString(NativeProtocol.RESULT_ARGS_GRAPH_DOMAIN)
      return AccessToken(
          token,
          applicationId,
          userId,
          permissions,
          null,
          null,
          source,
          expires,
          Date(),
          dataAccessExpirationTime,
          graphDomain)
    }

    /**
     * WARNING: This feature is currently in development and not intended for external usage.
     *
     * @param bundle
     * @param expectedNonce the nonce expected to have created with the AuthenticationToken
     * @return AuthenticationToken
     * @throws FacebookException
     */
    @JvmStatic
    @Throws(FacebookException::class)
    fun createAuthenticationTokenFromWebBundle(
        bundle: Bundle,
        expectedNonce: String?
    ): AuthenticationToken? {
      val authenticationTokenString = bundle.getString(AuthenticationToken.AUTHENTICATION_TOKEN_KEY)
      return if (authenticationTokenString == null ||
          authenticationTokenString.isEmpty() ||
          expectedNonce == null ||
          expectedNonce.isEmpty()) {
        null
      } else
          try {
            /**
             * TODO T96881697: create factory class for authentication token, remove this try-catch
             * and replace AuthenticationToken with factory method
             */
            AuthenticationToken(authenticationTokenString, expectedNonce)
          } catch (ex: Exception) {
            throw FacebookException(ex.message, ex)
          }
    }

    @JvmStatic
    @Throws(FacebookException::class)
    fun createAccessTokenFromWebBundle(
        requestedPermissions: Collection<String?>?,
        bundle: Bundle,
        source: AccessTokenSource?,
        applicationId: String
    ): AccessToken? {
      var grantedRequestedPermissions = requestedPermissions
      val expires = getBundleLongAsDate(bundle, AccessToken.EXPIRES_IN_KEY, Date())
      val token = bundle.getString(AccessToken.ACCESS_TOKEN_KEY) ?: return null
      val dataAccessExpirationTime =
          getBundleLongAsDate(bundle, AccessToken.DATA_ACCESS_EXPIRATION_TIME, Date(0))

      // With Login v4, we now get back the actual permissions granted, so update the permissions
      // to be the real thing
      val grantedPermissions = bundle.getString("granted_scopes")
      if (grantedPermissions != null && grantedPermissions.isNotEmpty()) {
        grantedRequestedPermissions = arrayListOf(*grantedPermissions.split(",").toTypedArray())
      }
      val deniedPermissions = bundle.getString("denied_scopes")
      var declinedPermissions: List<String?>? = null
      if (deniedPermissions != null && deniedPermissions.isNotEmpty()) {
        declinedPermissions = arrayListOf(*deniedPermissions.split(",").toTypedArray())
      }
      val expiredScopes = bundle.getString("expired_scopes")
      var expiredPermissions: List<String?>? = null
      if (expiredScopes != null && expiredScopes.isNotEmpty()) {
        expiredPermissions = arrayListOf(*expiredScopes.split(",").toTypedArray())
      }
      if (isNullOrEmpty(token)) {
        return null
      }
      val graphDomain = bundle.getString(AccessToken.GRAPH_DOMAIN)
      val signedRequest = bundle.getString("signed_request")
      val userId = getUserIDFromSignedRequest(signedRequest)
      return AccessToken(
          token,
          applicationId,
          userId,
          grantedRequestedPermissions,
          declinedPermissions,
          expiredPermissions,
          source,
          expires,
          Date(),
          dataAccessExpirationTime,
          graphDomain)
    }

    @JvmStatic
    @Throws(FacebookException::class)
    fun getUserIDFromSignedRequest(signedRequest: String?): String {
      if (signedRequest == null || signedRequest.isEmpty()) {
        throw FacebookException(NO_SIGNED_REQUEST_ERROR_MESSAGE)
      }
      try {
        val signatureAndPayload = signedRequest.split(".").toTypedArray()
        if (signatureAndPayload.size == 2) {
          val data = Base64.decode(signatureAndPayload[1], Base64.DEFAULT)
          val dataStr = String(data, Charsets.UTF_8)
          val jsonObject = JSONObject(dataStr)
          return jsonObject.getString("user_id")
        }
      } catch (ex: UnsupportedEncodingException) {} catch (ex: JSONException) {}
      throw FacebookException(NO_USER_ID_ERROR_MESSAGE)
    }
  }
}


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

package com.facebook.login

import com.facebook.AccessToken
import com.facebook.AuthenticationToken

/**
 * Represents the results of a login operation.
 *
 * @property accessToken The new access token.
 * @property authenticationToken The new authentication token
 * @property recentlyGrantedPermissions The recently granted permissions.
 * @property recentlyDeniedPermissions The recently denied permissions.
 */
data class LoginResult
@JvmOverloads
constructor(
    val accessToken: AccessToken,
    val authenticationToken: AuthenticationToken? = null,
    val recentlyGrantedPermissions: Set<String>,
    val recentlyDeniedPermissions: Set<String>
)


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

package com.facebook.login

import android.content.Context
import android.os.Bundle
import com.facebook.internal.NativeProtocol
import com.facebook.internal.PlatformServiceClient

internal class LoginStatusClient(
    context: Context,
    applicationId: String,
    private val loggerRef: String,
    private val graphApiVersion: String,
    private val toastDurationMs: Long,
    nonce: String?
) :
    PlatformServiceClient(
        context,
        NativeProtocol.MESSAGE_GET_LOGIN_STATUS_REQUEST,
        NativeProtocol.MESSAGE_GET_LOGIN_STATUS_REPLY,
        NativeProtocol.PROTOCOL_VERSION_20170411,
        applicationId,
        nonce) {
  override fun populateRequestBundle(data: Bundle) {
    data.putString(NativeProtocol.EXTRA_LOGGER_REF, loggerRef)
    data.putString(NativeProtocol.EXTRA_GRAPH_API_VERSION, graphApiVersion)
    data.putLong(NativeProtocol.EXTRA_TOAST_DURATION_MS, toastDurationMs)
  }

  companion object {
    const val DEFAULT_TOAST_DURATION_MS = 5000L
    internal fun newInstance(
        context: Context,
        applicationId: String,
        loggerRef: String,
        graphApiVersion: String,
        toastDurationMs: Long,
        nonce: String?
    ): LoginStatusClient =
        LoginStatusClient(
            context, applicationId, loggerRef, graphApiVersion, toastDurationMs, nonce)
  }
}


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

package com.facebook.login

import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.Bundle
import android.os.Parcel
import androidx.annotation.VisibleForTesting
import com.facebook.AccessTokenSource
import com.facebook.FacebookException
import com.facebook.FacebookSdk
import com.facebook.FacebookSdk.getExecutor
import com.facebook.FacebookServiceException
import com.facebook.internal.NativeProtocol
import com.facebook.internal.ServerProtocol.getErrorConnectionFailure
import com.facebook.internal.ServerProtocol.getErrorsProxyAuthDisabled
import com.facebook.internal.ServerProtocol.getErrorsUserCanceled
import com.facebook.internal.Utility.isNullOrEmpty

@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
abstract class NativeAppLoginMethodHandler : LoginMethodHandler {
  internal constructor(loginClient: LoginClient) : super(loginClient)
  internal constructor(source: Parcel) : super(source)

  /**
   * handle the success response from the initial request when user confirms on GDP
   *
   * @param request initial request
   * @param extras data returned from initial request
   */
  private fun processSuccessResponse(request: LoginClient.Request, extras: Bundle) {
    if (extras.containsKey("code") && !isNullOrEmpty(extras.getString("code"))) {
      // if contains "code" which mean this is code flow and need to exchange for token
      getExecutor().execute {
        try {
          val processedExtras = processCodeExchange(request, extras)
          handleResultOk(request, processedExtras)
        } catch (ex: FacebookServiceException) {
          val requestError = ex.requestError
          handleResultError(
              request,
              requestError.errorType,
              requestError.errorMessage,
              requestError.errorCode.toString())
        } catch (ex: FacebookException) {
          handleResultError(request, null, ex.message, null)
        }
      }
    } else {
      // Lightweight Login will go through this flow
      handleResultOk(request, extras)
    }
  }

  abstract override fun tryAuthorize(request: LoginClient.Request): Int

  open val tokenSource: AccessTokenSource = AccessTokenSource.FACEBOOK_APPLICATION_WEB

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
    val request = loginClient.pendingRequest
    if (data == null) {
      // This happens if the user presses 'Back'.
      completeLogin(LoginClient.Result.createCancelResult(request, "Operation canceled"))
    } else if (resultCode == Activity.RESULT_CANCELED) {
      handleResultCancel(request, data)
    } else if (resultCode != Activity.RESULT_OK) {
      completeLogin(
          LoginClient.Result.createErrorResult(
              request, "Unexpected resultCode from authorization.", null))
    } else {
      val extras = data.extras
      if (extras == null) {
        completeLogin(
            LoginClient.Result.createErrorResult(
                request, "Unexpected null from returned authorization data.", null))
        return true
      }
      val error = getError(extras)
      val errorCode = extras["error_code"]?.toString()
      val errorMessage = getErrorMessage(extras)
      val e2e = extras.getString(NativeProtocol.FACEBOOK_PROXY_AUTH_E2E_KEY)
      if (!isNullOrEmpty(e2e)) {
        logWebLoginCompleted(e2e)
      }
      if (error == null && errorCode == null && errorMessage == null && request != null) {
        processSuccessResponse(request, extras)
      } else {
        handleResultError(request, error, errorMessage, errorCode)
      }
    }
    return true
  }

  private fun completeLogin(outcome: LoginClient.Result?) {
    if (outcome != null) {
      loginClient.completeAndValidate(outcome)
    } else {
      loginClient.tryNextHandler()
    }
  }

  protected open fun handleResultError(
      request: LoginClient.Request?,
      error: String?,
      errorMessage: String?,
      errorCode: String?
  ) {
    if (error != null && error == "logged_out") {
      CustomTabLoginMethodHandler.calledThroughLoggedOutAppSwitch = true
      completeLogin(null)
    } else if (getErrorsProxyAuthDisabled().contains(error)) {
      completeLogin(null)
    } else if (getErrorsUserCanceled().contains(error)) {
      completeLogin(LoginClient.Result.createCancelResult(request, null))
    } else {
      completeLogin(LoginClient.Result.createErrorResult(request, error, errorMessage, errorCode))
    }
  }

  protected open fun handleResultOk(request: LoginClient.Request, extras: Bundle) {
    try {
      val token =
          createAccessTokenFromWebBundle(
              request.permissions, extras, tokenSource, request.applicationId)
      val authenticationToken = createAuthenticationTokenFromWebBundle(extras, request.nonce)
      completeLogin(
          LoginClient.Result.createCompositeTokenResult(request, token, authenticationToken))
    } catch (ex: FacebookException) {
      completeLogin(LoginClient.Result.createErrorResult(request, null, ex.message))
    }
  }

  protected open fun handleResultCancel(request: LoginClient.Request?, data: Intent) {
    val extras = data.extras
    val error = getError(extras)
    val errorCode: String? = extras?.get("error_code")?.toString()

    // If the device has lost network, the result will be a cancel with a connection failure
    // error. We want our consumers to be notified of this as an error so they can tell their
    // users to "reconnect and try again".
    if (getErrorConnectionFailure() == errorCode) {
      val errorMessage = getErrorMessage(extras)
      completeLogin(LoginClient.Result.createErrorResult(request, error, errorMessage, errorCode))
      return
    }
    completeLogin(LoginClient.Result.createCancelResult(request, error))
  }

  protected open fun getError(extras: Bundle?): String? {
    return extras?.getString("error") ?: extras?.getString("error_type")
  }

  protected open fun getErrorMessage(extras: Bundle?): String? {
    return extras?.getString("error_message") ?: extras?.getString("error_description")
  }

  protected open fun tryIntent(intent: Intent?, requestCode: Int): Boolean {
    if (intent == null || !isCallable(intent)) {
      return false
    }

    val loginFragment = loginClient.fragment as? LoginFragment
    loginFragment?.launcher?.launch(intent) ?: return false

    return true
  }

  private fun isCallable(intent: Intent): Boolean {
    val list: List<ResolveInfo> =
        FacebookSdk.getApplicationContext()
            .packageManager
            .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
    return list.isNotEmpty()
  }
}


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

package com.facebook.login

object NonceUtil {
  @JvmStatic
  fun isValidNonce(nonce: String?): Boolean {
    if (nonce.isNullOrEmpty()) {
      return false
    }

    val hasWhiteSpace = nonce.indexOf(' ') >= 0
    return !hasWhiteSpace
  }
}


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

package com.facebook.login

import android.os.Bundle
import android.util.Base64
import com.facebook.FacebookException
import com.facebook.FacebookSdk
import com.facebook.GraphRequest
import com.facebook.HttpMethod
import java.security.MessageDigest

internal object PKCEUtil {

  /**
   * codeVerifier: high-entropy cryptographic random STRING using the unreserved characters [A-Z] /
   * [a-z] / [0-9] / "-" / "." / "_" / "~" from Section 2.3 of [RFC3986], with a minimum length of
   * 43 characters and a maximum length of 128 characters. Doc Reference:
   * https://datatracker.ietf.org/doc/html/rfc7636
   */
  @JvmStatic
  fun isValidCodeVerifier(codeVerifier: String?): Boolean {
    if (codeVerifier.isNullOrEmpty() || codeVerifier.length < 43 || codeVerifier.length > 128) {
      return false
    }

    val regex = Regex("^[-._~A-Za-z0-9]+\$")
    return regex matches codeVerifier
  }

  /**
   * Generate a code verifier which follows the pattern [A-Z] / [a-z] / [0-9] / "-" / "." / "_" /
   * "~" and with a minimum length of 43 characters and a maximum length of 128 characters
   */
  @JvmStatic
  fun generateCodeVerifier(): String {
    val random43to128 = (43..128).random()
    val allowedCharSet = ('a'..'z') + ('A'..'Z') + ('0'..'9') + ('-') + ('.') + ('_') + ('~')
    return List(random43to128) { allowedCharSet.random() }.joinToString("")
  }

  /**
   * Returns the code challenge of the code verifier
   * @param codeVerifier the original code verifier
   * @param codeChallengeMethod the supplied codeChallengeMethod
   */
  @JvmStatic
  @Throws(FacebookException::class)
  fun generateCodeChallenge(
      codeVerifier: String,
      codeChallengeMethod: CodeChallengeMethod
  ): String {
    if (!isValidCodeVerifier(codeVerifier)) {
      throw FacebookException("Invalid Code Verifier.")
    }

    if (codeChallengeMethod == CodeChallengeMethod.PLAIN) {
      return codeVerifier
    }

    return try {
      // try to generate challenge with S256
      val bytes: ByteArray = codeVerifier.toByteArray(Charsets.US_ASCII)
      val messageDigest = MessageDigest.getInstance("SHA-256")
      messageDigest.update(bytes, 0, bytes.size)
      val digest = messageDigest.digest()

      Base64.encodeToString(digest, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
    } catch (ex: Exception) {
      throw FacebookException(ex)
    }
  }

  /**
   * Create the GraphRequest of
   * @param authorizationCode the server generated code needed for code exchange, one time use
   * @param redirectUri the uri specified during the code request
   * @param codeVerifier the original code verifier
   */
  @JvmStatic
  fun createCodeExchangeRequest(
      authorizationCode: String,
      redirectUri: String,
      codeVerifier: String
  ): GraphRequest {
    val parameters = Bundle()
    parameters.putString("code", authorizationCode)
    parameters.putString("client_id", FacebookSdk.getApplicationId())
    parameters.putString("redirect_uri", redirectUri)
    parameters.putString("code_verifier", codeVerifier)
    val graphRequest = GraphRequest.newGraphPathRequest(null, "oauth/access_token", null)
    graphRequest.httpMethod = HttpMethod.GET
    graphRequest.parameters = parameters
    return graphRequest
  }
}


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

package com.facebook.login

import android.app.Activity
import android.content.Intent

internal interface StartActivityDelegate {
  fun startActivityForResult(intent: Intent, requestCode: Int)
  val activityContext: Activity?
}


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

package com.facebook.login

import android.content.Context
import android.os.Bundle
import android.os.Parcel
import android.text.TextUtils
import android.webkit.CookieSyncManager
import androidx.annotation.RestrictTo
import androidx.annotation.VisibleForTesting
import com.facebook.AccessToken.Companion.getCurrentAccessToken
import com.facebook.AccessTokenSource
import com.facebook.FacebookException
import com.facebook.FacebookOperationCanceledException
import com.facebook.FacebookSdk
import com.facebook.FacebookSdk.getAutoLogAppEventsEnabled
import com.facebook.FacebookSdk.getSdkVersion
import com.facebook.FacebookServiceException
import com.facebook.appevents.AppEventsConstants
import com.facebook.internal.ServerProtocol
import com.facebook.internal.Utility.clearFacebookCookies
import com.facebook.internal.Utility.isNullOrEmpty

/** This class is for internal use. SDK users should not access it directly. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
abstract class WebLoginMethodHandler : LoginMethodHandler {
  private var e2e: String? = null
  abstract val tokenSource: AccessTokenSource

  internal constructor(loginClient: LoginClient) : super(loginClient)
  internal constructor(source: Parcel) : super(source)

  protected open fun getSSODevice(): String? = null

  protected open fun getParameters(request: LoginClient.Request): Bundle {
    val parameters = Bundle()
    if (!isNullOrEmpty(request.permissions)) {
      val scope = TextUtils.join(",", request.permissions)
      parameters.putString(ServerProtocol.DIALOG_PARAM_SCOPE, scope)
      addLoggingExtra(ServerProtocol.DIALOG_PARAM_SCOPE, scope)
    }
    val audience = request.defaultAudience ?: DefaultAudience.NONE
    parameters.putString(
        ServerProtocol.DIALOG_PARAM_DEFAULT_AUDIENCE, audience.nativeProtocolAudience)
    parameters.putString(ServerProtocol.DIALOG_PARAM_STATE, getClientState(request.authId))
    val previousToken = getCurrentAccessToken()
    val previousTokenString = previousToken?.token
    if (previousTokenString != null && previousTokenString == loadCookieToken()) {
      parameters.putString(ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN, previousTokenString)
      // Don't log the actual access token, just its presence or absence.
      addLoggingExtra(
          ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN, AppEventsConstants.EVENT_PARAM_VALUE_YES)
    } else {
      // The call to clear cookies will create the first instance of CookieSyncManager if
      // necessary
      loginClient.activity?.let { clearFacebookCookies(it) }
      addLoggingExtra(
          ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN, AppEventsConstants.EVENT_PARAM_VALUE_NO)
    }
    parameters.putString(ServerProtocol.DIALOG_PARAM_CBT, System.currentTimeMillis().toString())
    parameters.putString(
        ServerProtocol.DIALOG_PARAM_IES, if (getAutoLogAppEventsEnabled()) "1" else "0")
    return parameters
  }

  protected open fun addExtraParameters(parameters: Bundle, request: LoginClient.Request): Bundle {
    parameters.putString(ServerProtocol.DIALOG_PARAM_REDIRECT_URI, getRedirectUrl())
    if (request.isInstagramLogin) {
      parameters.putString(ServerProtocol.DIALOG_PARAM_APP_ID, request.applicationId)
    } else {
      // Client id is a legacy name. IG Login doesn't support it. This line is kept
      // for FB Login for consistency with old SDKs
      parameters.putString(ServerProtocol.DIALOG_PARAM_CLIENT_ID, request.applicationId)
    }
    parameters.putString(ServerProtocol.DIALOG_PARAM_E2E, LoginClient.getE2E())
    if (request.isInstagramLogin) {
      parameters.putString(
          ServerProtocol.DIALOG_PARAM_RESPONSE_TYPE,
          ServerProtocol.DIALOG_RESPONSE_TYPE_TOKEN_AND_SCOPES)
    } else {
      if (request.permissions.contains(LoginConfiguration.OPENID)) {
        parameters.putString(ServerProtocol.DIALOG_PARAM_NONCE, request.nonce)
      }
      parameters.putString(
          ServerProtocol.DIALOG_PARAM_RESPONSE_TYPE,
          ServerProtocol.DIALOG_RESPONSE_TYPE_ID_TOKEN_AND_SIGNED_REQUEST)
    }

    // PKCE params
    parameters.putString(ServerProtocol.DIALOG_PARAM_CODE_CHALLENGE, request.codeChallenge)
    parameters.putString(
        ServerProtocol.DIALOG_PARAM_CODE_CHALLENGE_METHOD, request.codeChallengeMethod?.name)

    parameters.putString(
        ServerProtocol.DIALOG_PARAM_RETURN_SCOPES, ServerProtocol.DIALOG_RETURN_SCOPES_TRUE)
    parameters.putString(ServerProtocol.DIALOG_PARAM_AUTH_TYPE, request.authType)
    parameters.putString(ServerProtocol.DIALOG_PARAM_LOGIN_BEHAVIOR, request.loginBehavior.name)
    parameters.putString(ServerProtocol.DIALOG_PARAM_SDK_VERSION, "android-${getSdkVersion()}")
    if (getSSODevice() != null) {
      parameters.putString(ServerProtocol.DIALOG_PARAM_SSO_DEVICE, getSSODevice())
    }
    parameters.putString(
        ServerProtocol.DIALOG_PARAM_CUSTOM_TABS_PREFETCHING,
        if (FacebookSdk.hasCustomTabsPrefetching) "1" else "0")
    if (request.isFamilyLogin) {
      parameters.putString(ServerProtocol.DIALOG_PARAM_FX_APP, request.loginTargetApp.toString())
    }
    if (request.shouldSkipAccountDeduplication()) {
      parameters.putString(ServerProtocol.DIALOG_PARAM_SKIP_DEDUPE, "true")
    }

    // Set Login Connect parameters if they are present
    if (request.messengerPageId != null) {
      parameters.putString(ServerProtocol.DIALOG_PARAM_MESSENGER_PAGE_ID, request.messengerPageId)
      parameters.putString(
          ServerProtocol.DIALOG_PARAM_RESET_MESSENGER_STATE,
          if (request.resetMessengerState) "1" else "0")
    }
    return parameters
  }

  @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
  open fun onComplete(request: LoginClient.Request, values: Bundle?, error: FacebookException?) {
    var outcome: LoginClient.Result
    val loginClient = loginClient
    e2e = null
    if (values != null) {
      // Actual e2e we got from the dialog should be used for logging.
      if (values.containsKey(ServerProtocol.DIALOG_PARAM_E2E)) {
        e2e = values.getString(ServerProtocol.DIALOG_PARAM_E2E)
      }
      try {
        val token =
            createAccessTokenFromWebBundle(
                request.permissions, values, tokenSource, request.applicationId)
        val authenticationToken = createAuthenticationTokenFromWebBundle(values, request.nonce)
        outcome =
            LoginClient.Result.createCompositeTokenResult(
                loginClient.pendingRequest, token, authenticationToken)

        // Ensure any cookies set by the dialog are saved
        // This is to work around a bug where CookieManager may fail to instantiate if
        // CookieSyncManager has never been created.
        if (loginClient.activity != null) {
          try {
            val syncManager = CookieSyncManager.createInstance(loginClient.activity)
            syncManager.sync()
          } catch (e: Exception) {
            // A crash happens in clearing the cookies. It's likely that webview is not available.
          }
          if (token != null) {
            saveCookieToken(token.token)
          }
        }
      } catch (ex: FacebookException) {
        outcome = LoginClient.Result.createErrorResult(loginClient.pendingRequest, null, ex.message)
      }
    } else {
      if (error is FacebookOperationCanceledException) {
        outcome =
            LoginClient.Result.createCancelResult(
                loginClient.pendingRequest, USER_CANCELED_LOG_IN_ERROR_MESSAGE)
      } else {
        // Something went wrong, don't log a completion event since it will skew timing
        // results.
        e2e = null
        var errorCode: String? = null
        var errorMessage = error?.message
        if (error is FacebookServiceException) {
          val requestError = error.requestError
          errorCode = requestError.errorCode.toString()
          errorMessage = requestError.toString()
        }
        outcome =
            LoginClient.Result.createErrorResult(
                loginClient.pendingRequest, null, errorMessage, errorCode)
      }
    }
    if (!isNullOrEmpty(e2e)) {
      logWebLoginCompleted(e2e)
    }
    loginClient.completeAndValidate(outcome)
  }

  private fun loadCookieToken(): String? {
    val context: Context = loginClient.activity ?: FacebookSdk.getApplicationContext()
    val sharedPreferences =
        context.getSharedPreferences(WEB_VIEW_AUTH_HANDLER_STORE, Context.MODE_PRIVATE)
    return sharedPreferences.getString(WEB_VIEW_AUTH_HANDLER_TOKEN_KEY, "")
  }

  private fun saveCookieToken(token: String) {
    val context: Context = loginClient.activity ?: FacebookSdk.getApplicationContext()
    context
        .getSharedPreferences(WEB_VIEW_AUTH_HANDLER_STORE, Context.MODE_PRIVATE)
        .edit()
        .putString(WEB_VIEW_AUTH_HANDLER_TOKEN_KEY, token)
        .apply()
  }

  companion object {
    private const val WEB_VIEW_AUTH_HANDLER_STORE =
        "com.facebook.login.AuthorizationClient.WebViewAuthHandler.TOKEN_STORE_KEY"
    private const val WEB_VIEW_AUTH_HANDLER_TOKEN_KEY = "TOKEN"
  }
}


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

package com.facebook.login

import android.content.Context
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import androidx.annotation.RestrictTo
import com.facebook.AccessTokenSource
import com.facebook.FacebookException
import com.facebook.internal.FacebookDialogFragment
import com.facebook.internal.ServerProtocol
import com.facebook.internal.Utility
import com.facebook.internal.WebDialog

/** This class is for internal use. SDK users should not access it directly. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
open class WebViewLoginMethodHandler : WebLoginMethodHandler {
  var loginDialog: WebDialog? = null
  var e2e: String? = null

  constructor(loginClient: LoginClient) : super(loginClient)

  override val nameForLogging = "web_view"

  override val tokenSource: AccessTokenSource = AccessTokenSource.WEB_VIEW

  override fun needsInternetPermission(): Boolean = true

  override fun cancel() {
    if (loginDialog != null) {
      loginDialog?.cancel()
      loginDialog = null
    }
  }

  override fun tryAuthorize(request: LoginClient.Request): Int {
    val parameters = getParameters(request)

    val listener =
        object : WebDialog.OnCompleteListener {
          override fun onComplete(values: Bundle?, error: FacebookException?) =
              onWebDialogComplete(request, values, error)
        }

    e2e = LoginClient.getE2E()
    addLoggingExtra(ServerProtocol.DIALOG_PARAM_E2E, e2e)

    val fragmentActivity = loginClient.activity ?: return 0
    val isChromeOS = Utility.isChromeOS(fragmentActivity)

    val builder =
        AuthDialogBuilder(fragmentActivity, request.applicationId, parameters)
            .setE2E(e2e as String)
            .setIsChromeOS(isChromeOS)
            .setAuthType(request.authType)
            .setLoginBehavior(request.loginBehavior)
            .setLoginTargetApp(request.loginTargetApp)
            .setFamilyLogin(request.isFamilyLogin)
            .setShouldSkipDedupe(request.shouldSkipAccountDeduplication())
            .setOnCompleteListener(listener)
    loginDialog = builder.build()

    val dialogFragment = FacebookDialogFragment()
    dialogFragment.retainInstance = true
    dialogFragment.innerDialog = loginDialog
    dialogFragment.show(fragmentActivity.getSupportFragmentManager(), FacebookDialogFragment.TAG)

    return 1
  }

  fun onWebDialogComplete(
      request: LoginClient.Request,
      values: Bundle?,
      error: FacebookException?
  ) {
    super.onComplete(request, values, error)
  }

  inner class AuthDialogBuilder : WebDialog.Builder {

    private var redirect_uri = ServerProtocol.DIALOG_REDIRECT_URI
    private var loginBehavior = LoginBehavior.NATIVE_WITH_FALLBACK
    private var targetApp = LoginTargetApp.FACEBOOK
    private var isFamilyLogin = false
    private var shouldSkipDedupe = false

    lateinit var e2e: String
    lateinit var authType: String

    constructor(
        context: Context,
        applicationId: String,
        parameters: Bundle
    ) : super(context, applicationId, OAUTH_DIALOG, parameters)

    fun setE2E(e2e: String): AuthDialogBuilder {
      this.e2e = e2e
      return this
    }

    /**
     * @deprecated This is no longer used
     * @return the AuthDialogBuilder
     */
    fun setIsRerequest(isRerequest: Boolean): AuthDialogBuilder = this

    fun setIsChromeOS(isChromeOS: Boolean): AuthDialogBuilder {
      this.redirect_uri =
          if (isChromeOS) ServerProtocol.DIALOG_REDIRECT_CHROME_OS_URI
          else ServerProtocol.DIALOG_REDIRECT_URI
      return this
    }

    fun setAuthType(authType: String): AuthDialogBuilder {
      this.authType = authType
      return this
    }

    fun setLoginBehavior(loginBehavior: LoginBehavior): AuthDialogBuilder {
      this.loginBehavior = loginBehavior
      return this
    }

    fun setLoginTargetApp(targetApp: LoginTargetApp): AuthDialogBuilder {
      this.targetApp = targetApp
      return this
    }

    fun setFamilyLogin(isFamilyLogin: Boolean): AuthDialogBuilder {
      this.isFamilyLogin = isFamilyLogin
      return this
    }

    fun setShouldSkipDedupe(shouldSkip: Boolean): AuthDialogBuilder {
      this.shouldSkipDedupe = shouldSkip
      return this
    }

    override fun build(): WebDialog {
      val parameters = this.parameters as Bundle
      parameters.putString(ServerProtocol.DIALOG_PARAM_REDIRECT_URI, this.redirect_uri)
      parameters.putString(ServerProtocol.DIALOG_PARAM_CLIENT_ID, this.applicationId)
      parameters.putString(ServerProtocol.DIALOG_PARAM_E2E, this.e2e)
      parameters.putString(
          ServerProtocol.DIALOG_PARAM_RESPONSE_TYPE,
          if (this.targetApp == LoginTargetApp.INSTAGRAM)
              ServerProtocol.DIALOG_RESPONSE_TYPE_TOKEN_AND_SCOPES
          else ServerProtocol.DIALOG_RESPONSE_TYPE_TOKEN_AND_SIGNED_REQUEST)
      parameters.putString(
          ServerProtocol.DIALOG_PARAM_RETURN_SCOPES, ServerProtocol.DIALOG_RETURN_SCOPES_TRUE)
      parameters.putString(ServerProtocol.DIALOG_PARAM_AUTH_TYPE, this.authType)
      parameters.putString(ServerProtocol.DIALOG_PARAM_LOGIN_BEHAVIOR, this.loginBehavior.name)
      if (this.isFamilyLogin) {
        parameters.putString(ServerProtocol.DIALOG_PARAM_FX_APP, this.targetApp.toString())
      }
      if (this.shouldSkipDedupe) {
        parameters.putString(ServerProtocol.DIALOG_PARAM_SKIP_DEDUPE, "true")
      }

      return WebDialog.newInstance(
          this.context as Context, OAUTH_DIALOG, parameters, theme, this.targetApp, listener)
    }
  }

  companion object {
    private const val OAUTH_DIALOG = "oauth"
    @JvmField
    val CREATOR =
        object : Parcelable.Creator<WebViewLoginMethodHandler> {

          override fun createFromParcel(source: Parcel): WebViewLoginMethodHandler {
            return WebViewLoginMethodHandler(source)
          }

          override fun newArray(size: Int): Array<WebViewLoginMethodHandler?> {
            return arrayOfNulls(size)
          }
        }
  }

  constructor(source: Parcel) : super(source) {
    this.e2e = source.readString()
  }

  override fun describeContents(): Int = 0

  override fun writeToParcel(dest: Parcel, flags: Int) {
    super.writeToParcel(dest, flags)
    dest.writeString(this.e2e)
  }
}


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

package com.facebook.share

/**
 * Interface for builders related to sharing.
 *
 * @param <M> The class of the object to be built.
 * @param <B> The concrete builder class.
 */
fun interface ShareBuilder<M, B : ShareBuilder<M, B>> {
  /**
   * Builds the object.
   *
   * @return The built object.
   */
  fun build(): M
}


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

package com.facebook.share

/**
 * The common interface for components that initiate sharing.
 *
 * @see com.facebook.share.widget.ShareDialog
 *
 * @see com.facebook.share.widget.MessageDialog
 */
interface Sharer {
  /**
   * Specifies whether the sharer should fail if it finds an error with the share content. If false,
   * the share dialog 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.
   *
   * @return A Boolean value.
   */
  fun getShouldFailOnDataError(): Boolean

  /**
   * Specifies whether the sharer should fail if it finds an error with the share content. If false,
   * the share dialog 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.
   *
   * @param shouldFailOnDataError whether the dialog should fail if it finds an error.
   */
  fun setShouldFailOnDataError(shouldFailOnDataError: Boolean)

  /** Helper object for handling the result from a share dialog or share operation */
  class Result
  /**
   * Constructor.
   *
   * @param postId the resulting post id.
   */
  (
      /**
       * Returns the post id, if available.
       *
       * @return the post id.
       */
      val postId: String?
  )
}


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

package com.facebook.share.internal

import com.facebook.internal.DialogFeature
import com.facebook.internal.NativeProtocol

/**
 * com.facebook.share.internal is solely for the use of other packages within the Facebook SDK for
 * Android. Use of any of the classes in this package is unsupported, and they may be modified or
 * removed without warning at any time.
 */
enum class CameraEffectFeature(private val minVersion: Int) : DialogFeature {
  SHARE_CAMERA_EFFECT(NativeProtocol.PROTOCOL_VERSION_20170417);

  /** This method is for internal use only. */
  override fun getAction(): String = NativeProtocol.ACTION_CAMERA_EFFECT

  /** This method is for internal use only. */
  override fun getMinVersion(): Int = minVersion
}