import Foundation
import Capacitor
import AppsFlyerLib


@objc(AppsFlyerPlugin)
public class AppsFlyerPlugin: CAPPlugin, CAPBridgedPlugin {
    public let identifier = "AppsFlyerPlugin"
    public let jsName = "AppsFlyerPlugin"
    // CAPBridgedPlugin registers the plugin without a separate .m file.
    // SwiftPM doesn't support mixed Swift + Objective-C source in a single
    // target, so the legacy `AppsFlyerPlugin.m` (which used the CAP_PLUGIN
    // / CAP_PLUGIN_METHOD macros) was blocking SPM consumers entirely.
    // Keep this array in sync with @objc func methods exposed below.
    public let pluginMethods: [CAPPluginMethod] = [
        CAPPluginMethod(name: "initSDK", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "logEvent", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "setCustomerUserId", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "setCurrencyCode", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "updateServerUninstallToken", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "setAppInviteOneLink", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "setOneLinkCustomDomain", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "appendParametersToDeepLinkingURL", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "setResolveDeepLinkURLs", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "addPushNotificationDeepLinkPath", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "setSharingFilter", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "setSharingFilterForAllPartners", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "setAdditionalData", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "getAppsFlyerUID", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "anonymizeUser", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "stop", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "disableSKAdNetwork", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "disableAdvertisingIdentifier", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "disableCollectASA", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "setHost", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "generateInviteLink", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "validateAndLogInAppPurchaseAndroid", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "disableAppSetId", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "validateAndLogInAppPurchaseIos", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getSdkVersion", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "enableFacebookDeferredApplinks", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "sendPushNotificationData", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "setCurrentDeviceLanguage", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "logCrossPromoteImpression", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "setUserEmails", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "logLocation", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "setPhoneNumber", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "setPartnerData", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "logInvite", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "setSharingFilterForPartners", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "enableTCFDataCollection", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "setConsentData", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "startSDK", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "logAdRevenue", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "setConsentDataV2", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "isSDKStarted", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "isSDKStopped", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "validateAndLogInAppPurchaseV2", returnType: CAPPluginReturnPromise),
    ]
    private let APPSFLYER_PLUGIN_VERSION = "6.18.0"
    private var conversion = true
    private var oaoa = true
    private var udl = false
    private var hasSDKStarted: Bool = false
    
    override public func load() {
        
        NotificationCenter.default
            .addObserver(
                self,
                selector: #selector(self.handleUrlOpened(notification:)),
                name: Notification.Name.capacitorOpenURL,
                object: nil
            )
        NotificationCenter.default
            .addObserver(
                self,
                selector: #selector(self.handleUniversalLink(notification:)),
                name: Notification.Name.capacitorOpenUniversalLink,
                object: nil
            )
        
    }
    
    
    @objc func initSDK(_ call: CAPPluginCall){
        let appsflyer = AppsFlyerLib.shared()
        
        appsflyer
            .setPluginInfo(
                plugin: .capacitor,
                version: APPSFLYER_PLUGIN_VERSION,
                additionalParams: nil
            )
        guard  let devKey = call.getString(AppsFlyerConstants.AF_DEV_KEY) else{
            call.reject("devkey is missing")
            return
        }
        guard  let appID = call.getString(AppsFlyerConstants.AF_APP_ID) else{
            call.reject("appID is missing")
            return
        }
        let attInterval = call.getInt(AppsFlyerConstants.AF_ATT)
        
        let debug = call.getBool(AppsFlyerConstants.AF_DEBUG, false)
        let sandbox = call.getBool(AppsFlyerConstants.AF_SANDBOX, false)
        let receiptSandbox = call.getBool(
            AppsFlyerConstants.AF_RECEIPT_SANDBOX,
            false
        )
        let manualStart = call.getBool(
            AppsFlyerConstants.AF_MANUAL_START,
            false
        )
        
        conversion = call
            .getBool(AppsFlyerConstants.AF_CONVERSION_LISTENER, true)
        oaoa = call.getBool(AppsFlyerConstants.AF_OAOA, true)
        udl = call.getBool(AppsFlyerConstants.AF_UDL, false)
        
        appsflyer.isDebug = debug
        appsflyer.appsFlyerDevKey = devKey
        appsflyer.appleAppID = appID
        appsflyer.useUninstallSandbox = sandbox
        appsflyer.useReceiptValidationSandbox = receiptSandbox
        
        if let minTime = call.getInt(AppsFlyerConstants.AF_MIN_TIME){
            appsflyer.minTimeBetweenSessions = UInt(minTime)
        }
        
        if let timeout = call.getInt(AppsFlyerConstants.AF_DEEP_LINK_TIME_OUT){
            appsflyer.deepLinkTimeout = UInt(timeout)
        }
        
        if conversion || oaoa {
            appsflyer.delegate = self
        }
        
        if udl {
            appsflyer.deepLinkDelegate = self
        }
        
        reportBridgeReady()
        
#if !AFSDK_NO_IDFA
        if attInterval != nil {
            appsflyer
                .waitForATTUserAuthorization(
                    timeoutInterval: Double(attInterval!)
                )
        }
#endif
        
        if !manualStart {
            startSDK(call)
        } else {
            call
                .resolve(
                    ["res": "SDK initiated successfully. SDK has NOT started yet"]
                )
        }
    }
    
    @objc func startSDK(_ call: CAPPluginCall) {
        NotificationCenter.default
            .addObserver(
                self,
                selector: #selector(sendLaunch),
                name: UIApplication.didBecomeActiveNotification,
                object: nil
            )
        
        AppsFlyerLib.shared().start { [weak self] dictionary, error in
            if let error = error {
                call.reject(error.localizedDescription)
            } else {
                self?.hasSDKStarted = true
                call.resolve(["res": "success"])
            }
        }
    }
    
    @objc func logEvent(_ call: CAPPluginCall){
        guard let eventName = call.getString(AppsFlyerConstants.AF_EVENT_NAME) else{
            call.reject("missing event name")
            return
        }
        let eventValue = call.getObject(AppsFlyerConstants.AF_EVENT_VALUE)
        
        AppsFlyerLib
            .shared()
            .logEvent(name: eventName, values: eventValue) {  (
                response: [String : Any]?,
                error: Error?
            ) in
                if let response = response {
                    call.resolve(["res":response])
                }
                if let error = error {
                    call.reject(error.localizedDescription, nil, error)
                }
            }
    }
    
    @objc func setCustomerUserId(_ call: CAPPluginCall) {
        guard  let cuid = call.getString(AppsFlyerConstants.AF_CUID) else {
            call.reject("Invalid Customer User ID")
            return
        }
        AppsFlyerLib.shared().customerUserID = cuid
    }
    
    
    @objc func logAdRevenue(_ call: CAPPluginCall) {
        let adRevenueData = call.jsObjectRepresentation
        if adRevenueData.isEmpty {
            call
                .reject(
                    "adRevenueData is missing, the data mandatory to use this API."
                )
            return
        }
        
        // Parse the fields from the adRevenueData object
        guard let myMonetizationNetwork = adRevenueData[AppsFlyerConstants.AF_MONETIZATION_NETWORK] as? String else {
            call.reject("monetizationNetwork is missing")
            return
        }
        guard let myCurrencyIso4217Code = adRevenueData[AppsFlyerConstants.AF_CURRENCY_ISO4217_CODE] as? String else {
            call.reject("currencyIso4217Code is missing")
            return
        }
        guard let revenue = adRevenueData[AppsFlyerConstants.AF_REVENUE] as? NSNumber else {
            call.reject("revenue is missing or not a number")
            return
        }
        guard let mediationNetworkString = adRevenueData[AppsFlyerConstants.AF_MEDIATION_NETWORK] as? String else {
            call.reject("mediationNetwork is missing")
            return
        }
        
        let additionalParams = adRevenueData[AppsFlyerConstants.AF_ADDITIONAL_PARAMETERS] as? [String: Any]
        
        // Handle the mediation network mapping to AppsFlyer SDK enum
        guard let myMediationNetwork = MediationNetworkType(rawValue: mediationNetworkString) else {
            call.reject("Invalid mediation network")
            return
        }
        
        // Create the AFAdRevenueData instance
        let myAdRevenueData = AFAdRevenueData(
            monetizationNetwork: myMonetizationNetwork,
            mediationNetwork: myMediationNetwork,
            currencyIso4217Code: myCurrencyIso4217Code,
            eventRevenue: revenue
        )
        
        // Log the ad revenue to the AppsFlyer SDK
        AppsFlyerLib
            .shared()
            .logAdRevenue(
                myAdRevenueData,
                additionalParameters: additionalParams
            )
        
        call.resolve()
    }
    
    // Helper function to map mediation network string to the AppsFlyer enum
    private func MediationNetworkType(rawValue: String) -> MediationNetworkType? {
        switch rawValue {
        case "google_admob":
            return .googleAdMob
        case "fyber":
            return .fyber
        case "ironsource":
            return .ironSource
        case "applovin_max":
            return .applovinMax
        case "appodeal":
            return .appodeal
        case "admost":
            return .admost
        case "topon":
            return .topon
        case "tradplus":
            return .tradplus
        case "yandex":
            return .yandex
        case "chartboost":
            return .chartBoost
        case "unity":
            return .unity
        case "topon_pte":
            return .toponPte
        case "custom_mediation":
            return .custom
        case "direct_monetization_network":
            return .directMonetization
        default:
            return nil
        }
    }
    
    @objc func setCurrencyCode(_ call: CAPPluginCall){
        guard  let code = call.getString(AppsFlyerConstants.AF_CURRENCY_CODE) else {
            call.reject("Invalid Currency Code")
            return
        }
        AppsFlyerLib.shared().currencyCode = code
        
        
    }
    
    @objc func updateServerUninstallToken(_ call: CAPPluginCall){
        guard  let token = call.getString(AppsFlyerConstants.AF_TOKEN) else {
            call.reject("Invalid device token")
            
            return
        }
        
        guard let deviceTokenData = token.hexadecimalToData else{
            call.reject("Invalid device token")
            return
        }
        AppsFlyerLib.shared().registerUninstall(deviceTokenData)
    }
    
    @objc func setAppInviteOneLink(_ call: CAPPluginCall){
        guard  let id = call.getString(AppsFlyerConstants.AF_ONELINK_ID) else {
            call.reject("Onelink id is missing")
            return
        }
        AppsFlyerLib.shared().appInviteOneLinkID = id
        
    }
    
    @objc func setOneLinkCustomDomain(_ call: CAPPluginCall){
        guard  let arr = call.getArray(AppsFlyerConstants.AF_ONELINK_DOMAIN) else {
            call.reject("Domains are missing")
            return
        }
        var domains :[String] = []
        for dom in arr {
            domains.append(dom as! String)
        }
        AppsFlyerLib.shared().oneLinkCustomDomains = domains
        
    }
    
    @objc func appendParametersToDeepLinkingURL(_ call: CAPPluginCall){
        guard  let contains = call.getString(AppsFlyerConstants.AF_CONTAINS) else {
            return
        }
        guard  let parameters = call.getObject(AppsFlyerConstants.AF_PARAMETERS) else {
            return
        }
        var params: [String:String] = [:]
        for (k,v) in parameters{
            params[k] = (v as! String)
        }
        AppsFlyerLib
            .shared()
            .appendParametersToDeeplinkURL(
                contains: contains,
                parameters:params
            )
        
    }
    
    
    @objc func setResolveDeepLinkURLs(_ call: CAPPluginCall){
        guard  let arr = call.getArray(AppsFlyerConstants.AF_DEEPLINK_URLS) else {
            call.reject("URLs are missing")
            return
        }
        var urls :[String] = []
        for url in arr {
            urls.append(url as! String)
        }
        AppsFlyerLib.shared().resolveDeepLinkURLs = urls
        
    }
    
    @objc func addPushNotificationDeepLinkPath(_ call: CAPPluginCall){
        guard  let arr = call.getArray(AppsFlyerConstants.AF_PATH) else {
            call.reject("Path is missing")
            return
        }
        var path :[String] = []
        for p in arr {
            path.append(p as! String)
        }
        AppsFlyerLib.shared().addPushNotificationDeepLinkPath(path)
        
    }
    
    @available(*, deprecated, message: "Use setSharingFilterForPartners")
    @objc func setSharingFilter(_ call: CAPPluginCall){
        let filters = call.getArray(AppsFlyerConstants.AF_FILTERS , String.self)
        
        AppsFlyerLib.shared().sharingFilter = filters
        
    }
    
    @available(*, deprecated, message: "Use setSharingFilterForPartners")
    @objc func setSharingFilterForAllPartners(_ call: CAPPluginCall){
        
        AppsFlyerLib.shared().setSharingFilterForAllPartners()
        
    }
    
    @objc func setSharingFilterForPartners(_ call: CAPPluginCall){
        guard let filters = call.getArray(AppsFlyerConstants.AF_FILTERS , String.self) else{
            return call.reject("cannot extract the filters value")
        }
        
        AppsFlyerLib.shared().setSharingFilterForPartners(filters)
        
    }
    
    @objc func setAdditionalData(_ call: CAPPluginCall){
        guard  let data = call.getObject(AppsFlyerConstants.AF_ADDITIONAL_DATA) else {
            call.reject("Data is missing")
            return
        }
        AppsFlyerLib.shared().customData = data
        
    }
    
    
    @objc func getAppsFlyerUID(_ call: CAPPluginCall){
        let uid = AppsFlyerLib.shared().getAppsFlyerUID()
        call.resolve(["uid":uid])
        
    }
    
    @objc func setDisableNetworkData(_ call: CAPPluginCall){
        call.unavailable("Android only method - has no effact on iOS apps")
    }
    
    @objc func enableTCFDataCollection(_ call: CAPPluginCall){
        guard let shouldEnableTCFDataCollection = call.getBool(AppsFlyerConstants.AF_ENABLE_TCF_DATA_COLLECTION) else {
            call.reject("Missing boolean value shouldEnableTCFDataCollection")
            return
        }
        AppsFlyerLib
            .shared()
            .enableTCFDataCollection(shouldEnableTCFDataCollection)
    }
    
    @objc func setConsentData(_ call: CAPPluginCall) {
        guard let consentData = call.getObject("data") else {
            call.reject("Consent data is missing")
            return
        }
        
        let isUserSubjectToGDPR = consentData[AppsFlyerConstants.AF_IS_SUBJECTED_TO_DGPR] as? Bool ?? false
        let hasConsentForDataUsage = consentData[AppsFlyerConstants.AF_CONSENT_FOR_DATA_USAGE] as? Bool ?? false
        let hasConsentForAdsPersonalization = consentData[AppsFlyerConstants.AF_CONSENT_FOR_ADS_PERSONALIZATION] as? Bool ?? false
        
        let consentObject: AppsFlyerConsent
        if isUserSubjectToGDPR {
            consentObject = AppsFlyerConsent(
                forGDPRUserWithHasConsentForDataUsage: hasConsentForDataUsage,
                hasConsentForAdsPersonalization: hasConsentForAdsPersonalization
            )
        } else {
            consentObject = AppsFlyerConsent(nonGDPRUser: ())
        }
        
        AppsFlyerLib.shared().setConsentData(consentObject)
        
        call.resolve()
    }
    
    @objc func setConsentDataV2(_ call: CAPPluginCall) {
        // inner helper: convert Bool? to NSNumber?
        func toNSNumber(_ value: Bool?) -> NSNumber? {
            guard let value = value else { return nil }
            return NSNumber(value: value)
        }
        
        let isUserSubjectToGDPR = toNSNumber(call.getBool(AppsFlyerConstants.AF_IS_SUBJECTED_TO_DGPR))
        let hasConsentForDataUsage = toNSNumber(call.getBool(AppsFlyerConstants.AF_CONSENT_FOR_DATA_USAGE))
        let hasConsentForAdsPersonalization = toNSNumber(call.getBool(AppsFlyerConstants.AF_CONSENT_FOR_ADS_PERSONALIZATION))
        let hasConsentForAdStorage = toNSNumber(call.getBool(AppsFlyerConstants.AF_CONSENT_FOR_ADS_STORAGE))
        
        // Build consent options object and pass to AppsFlyer SDK
        let consentOptions = AppsFlyerConsent(
            isUserSubjectToGDPR: isUserSubjectToGDPR,
            hasConsentForDataUsage: hasConsentForDataUsage,
            hasConsentForAdsPersonalization: hasConsentForAdsPersonalization,
            hasConsentForAdStorage: hasConsentForAdStorage
        )
        
        AppsFlyerLib.shared().setConsentData(consentOptions)
        call.resolve()
    }
    
    @objc func anonymizeUser(_ call: CAPPluginCall){
        guard let anonymize = call.getBool(AppsFlyerConstants.AF_ANONYMIZE_USER) else{
            call.reject("Missing boolean value anonymizeUser")
            return
        }
        AppsFlyerLib.shared().anonymizeUser = anonymize
        
    }
    
    @objc func stop(_ call: CAPPluginCall){
        let stop = call.getBool(AppsFlyerConstants.AF_STOP)
        if stop != nil {
            AppsFlyerLib.shared().isStopped = stop!
        }
        call
            .resolve(
                [AppsFlyerConstants.AF_IS_STOP : AppsFlyerLib.shared().isStopped ]
            )
    }
    
    @objc func disableSKAdNetwork(_ call: CAPPluginCall){
        guard let disable = call.getBool(AppsFlyerConstants.AF_DISABLE_SKAD) else {
            call.reject("Missing boolean value shouldDisable")
            return
        }
        AppsFlyerLib.shared().disableSKAdNetwork = disable
        
    }
    
#if !AFSDK_NO_IDFA
    @objc func disableAdvertisingIdentifier(_ call: CAPPluginCall){
        guard let disable = call.getBool(AppsFlyerConstants.AF_DISABLE_SKAD) else {
            call.reject("Missing boolean value shouldDisable")
            return
        }
        AppsFlyerLib.shared().disableAdvertisingIdentifier = disable
        
    }
#endif
    
    @objc func disableCollectASA(_ call: CAPPluginCall){
        guard let disable = call.getBool(AppsFlyerConstants.AF_DISABLE_SKAD) else {
            call.reject("Missing boolean value shouldDisable")
            return
        }
        AppsFlyerLib.shared().disableCollectASA = disable
        
    }
    @objc func setHost(_ call: CAPPluginCall){
        let pre = call.getString(AppsFlyerConstants.AF_HOST_PREFIX)
        let post = call.getString(AppsFlyerConstants.AF_HOST_POSTFIX)
        if (pre != nil && post != nil) {
            AppsFlyerLib.shared().setHost(post!, withHostPrefix: pre!)
        } else {
            call.reject("Missing host prefix and/or host name")
        }
    }
    
    @objc func generateInviteLink(_ call: CAPPluginCall){
        AppsFlyerShareInviteHelper.generateInviteUrl(
            linkGenerator:
                {(
                    _ generator: AppsFlyerLinkGenerator
                ) -> AppsFlyerLinkGenerator in
                    if let channel = call.getString(AppsFlyerConstants.AF_CHANNEL){
                        generator.setChannel(channel)
                    }
                    if let brandDomain = call.getString(AppsFlyerConstants.AF_BRAND_DOMAIN){
                        generator.brandDomain = brandDomain
                    }
                    if let campaign = call.getString(AppsFlyerConstants.AF_CAMPAIGN){
                        generator.setCampaign(campaign)
                    }
                    if let referrerName = call.getString(
                        AppsFlyerConstants.AF_REFERRER_NAME
                    ){
                        generator.setReferrerName(referrerName)
                    }
                    if let referrerImageURL = call.getString(
                        AppsFlyerConstants.AF_REFERRER_IMAGE_URL
                    ){
                        generator.setReferrerImageURL(referrerImageURL)
                    }
                    if let referrerCustomerId = call.getString(
                        AppsFlyerConstants.AF_REFERRER_CUSTOMER_ID
                    ){
                        generator.setReferrerCustomerId(referrerCustomerId)
                    }
                    if let baseDeeplink = call.getString(
                        AppsFlyerConstants.AF_BASE_DEEPLINK
                    ){
                        generator.setBaseDeeplink(baseDeeplink)
                    }
                    if let addParameters = call.getObject(
                        AppsFlyerConstants.AF_ADD_PARAMETERS
                    ){
                        generator.addParameters(addParameters)
                    }
                    
                    return generator
                },
            completionHandler: {url in
                if url != nil{
                    call.resolve([AppsFlyerConstants.AF_LINK_READY: url!.absoluteString])
                }else{
                    call.reject("Failed to generate a link")
                }
            }
        )
        
    }
    @available(*, deprecated, message: "Use validateAndLogInAppPurchaseV2 instead")
    @objc func validateAndLogInAppPurchaseAndroid(_ call: CAPPluginCall){
        call.unavailable()
    }
    
    @available(*, deprecated, message: "Use validateAndLogInAppPurchaseV2 instead")
    @objc func validateAndLogInAppPurchaseIos(_ call: CAPPluginCall){
        let currency = call.getString(AppsFlyerConstants.AF_CURRENCY)
        let price = call.getString(AppsFlyerConstants.AF_PRICE)
        let _inAppPurchase = call.getString(
            AppsFlyerConstants.AF_IN_APP_PURCHASE
        )
        let transactionId = call.getString(AppsFlyerConstants.AF_TRANSACTION_ID)
        let additionalParameters = call.getObject(AppsFlyerConstants.AF_ADDITIONAL_PARAMETERS) ?? [:]
        
        if currency != nil && price != nil && _inAppPurchase != nil && transactionId != nil && currency != nil   {
            AppsFlyerLib.shared().validateAndLog(
                inAppPurchase: _inAppPurchase,
                price: price,
                currency: currency,
                transactionId: transactionId,
                additionalParameters: additionalParameters,
                success: {result in
                    call.resolve(["res":result])
                },
                failure: { error, result in
                    guard let emptyInApp = result as? [String:Any]
                    else
                    {
                        call.reject((error)?.localizedDescription ?? "error" )
                        return
                    }
                    call.reject((error)?.localizedDescription ?? "error" , emptyInApp.jsonStringRepresentation)
                })
        }else{
            call.reject("Missing some fields")
        }
        
        
    }
    
    @objc func getSdkVersion(_ call: CAPPluginCall){
        
        call.resolve(["res": AppsFlyerLib.shared().getSDKVersion()])
    }
    
    @objc func enableFacebookDeferredApplinks(_ call: CAPPluginCall){
        guard  let enable = call.getBool(AppsFlyerConstants.AF_FB) else {
            call.reject("missing boolean value: \(AppsFlyerConstants.AF_FB)")
            return
        }
        if enable{
#if canImport(FacebookCore)
            AppsFlyerLib.shared().enableFacebookDeferredApplinks(with: FBSDKAppLinkUtility.self)
            call.resolve(["res": "enabled"])
#else
            call.reject("Please install FBSDK First!")
#endif
        }else{
            AppsFlyerLib.shared().enableFacebookDeferredApplinks(with: nil)
            call.resolve(["res": "disabled"])
            
        }
        
    }
    
    @objc func sendPushNotificationData(_ call: CAPPluginCall){
        let json = call.getObject(AppsFlyerConstants.AF_PUSH_PAYLOAD)
        AppsFlyerLib.shared().handlePushNotification(json)
        
    }
    
    
    @objc func setCurrentDeviceLanguage(_ call: CAPPluginCall){
        guard let language = call.getString(AppsFlyerConstants.AF_LANGUAGE) else {
            call.reject("cannot extract the language value")
            return
        }
        AppsFlyerLib.shared().currentDeviceLanguage = language
        call.resolve(["res": "ok"])
        
    }
    
    @objc func logCrossPromoteImpression(_ call: CAPPluginCall){
        guard let appID = call.getString(AppsFlyerConstants.AF_APP_ID) else {
            call.reject("cannot extract the appID value")
            return
        }
        guard let campaign = call.getString(AppsFlyerConstants.AF_CAMPAIGN) else {
            call.reject("cannot extract the campaign value")
            return
        }
        guard let parameters = call.getObject(AppsFlyerConstants.AF_PARAMETERS) else {
            call.reject("cannot extract the parameters value")
            return
        }
        AppsFlyerCrossPromotionHelper
            .logCrossPromoteImpression(
                appID,
                campaign: campaign,
                parameters: parameters
            )
        call.resolve(["res": "ok"])
        
    }
    
    @objc func setUserEmails(_ call: CAPPluginCall){
        guard let emails = call.getArray(AppsFlyerConstants.AF_EMAILS, String.self) else {
            call.reject("cannot extract the emails value")
            return
        }
        if let enc = call.getBool(AppsFlyerConstants.AF_ENCODE) , enc == true{
            AppsFlyerLib
                .shared()
                .setUserEmails(emails, with: EmailCryptTypeSHA256)
            
        }else{
            AppsFlyerLib
                .shared()
                .setUserEmails(emails, with: EmailCryptTypeNone)
            
        }
        call.resolve(["res": "ok"])
        
    }
    
    @objc func logLocation(_ call: CAPPluginCall){
        guard let longitude = call.getDouble(AppsFlyerConstants.AF_LONGITUDE) else {
            call.reject("cannot extract the longitude value")
            return
        }
        guard let latitude = call.getDouble(AppsFlyerConstants.AF_LATITUDE) else {
            call.reject("cannot extract the longitude value")
            return
        }
        
        AppsFlyerLib
            .shared()
            .logLocation(longitude: longitude, latitude: latitude)
        call.resolve(["res": "ok"])
        
    }
    
    @objc func setPhoneNumber(_ call: CAPPluginCall){
        guard let phone = call.getString(AppsFlyerConstants.AF_PHONE) else {
            call.reject("cannot extract the phone value")
            return
        }
        
        AppsFlyerLib.shared().phoneNumber = phone
        call.resolve(["res": "ok"])
        
    }
    
    @objc func setPartnerData(_ call: CAPPluginCall){
        guard let data = call.getObject(AppsFlyerConstants.AF_DATA) else {
            call.reject("cannot extract the data value")
            return
        }
        guard let pid = call.getString(AppsFlyerConstants.AF_PARTNER_ID) else {
            call.reject("cannot extract the partnerId value")
            return
        }
        
        AppsFlyerLib.shared().setPartnerData(partnerId: pid, partnerInfo: data)
        call.resolve(["res": "ok"])
        
    }
    
    @objc func logInvite(_ call: CAPPluginCall){
        guard let data = call.getObject(AppsFlyerConstants.AF_EVENT_PARAMETERS) else {
            call.reject("cannot extract the eventParameters value")
            return
        }
        guard let channel = call.getString(AppsFlyerConstants.AF_CHANNEL) else {
            call.reject("cannot extract the channel value")
            return
        }
        
        AppsFlyerShareInviteHelper.logInvite(channel, parameters: data)
        call.resolve(["res": "ok"])
        
    }
    
    /**
     * Returns whether the AppsFlyer SDK has been started in the current session.
     */
    @objc func isSDKStarted(_ call: CAPPluginCall) {
        call.resolve(["isStarted": hasSDKStarted])
    }
    
    /**
     * Returns whether the AppsFlyer SDK is currently stopped.
     */
    @objc func isSDKStopped(_ call: CAPPluginCall) {
        call.resolve(["isStopped": AppsFlyerLib.shared().isStopped])
    }
    
    @objc func disableAppSetId(_ call: CAPPluginCall){
        call.unavailable()
    }
    
    @objc func validateAndLogInAppPurchaseV2(_ call: CAPPluginCall) {
        guard let purchaseDetailsMap = call.getObject(AppsFlyerConstants.AF_PURCHASE_DETAILS) else {
            call.reject("Purchase details are required")
            return
        }
        
        let additionalParameters = call.getObject(AppsFlyerConstants.AF_ADDITIONAL_PARAMETERS)
        
        // Validate required fields
        guard let purchaseTypeString = purchaseDetailsMap[AppsFlyerConstants.AF_PURCHASE_TYPE] as? String else {
            call.reject("Purchase type is required")
            return
        }
        guard let purchaseType = mapPurchaseType(purchaseTypeString) else {
            call.reject("Unkown purchase type")
            return
        }
        
        guard let purchaseToken = purchaseDetailsMap[AppsFlyerConstants.AF_PURCHASE_TOKEN] as? String else {
            call.reject("Purchase token is required")
            return
        }
        
        guard let productId = purchaseDetailsMap[AppsFlyerConstants.AF_PRODUCT_ID] as? String else {
            call.reject("Product ID is required")
            return
        }
        
        // Create a new instance of AFSDKPurchaseDetails with the required information
        let purchaseDetails: AFSDKPurchaseDetails = AFSDKPurchaseDetails(
            productId: productId,
            transactionId: purchaseToken,
            purchaseType: purchaseType
        )
        
        
        // For iOS, we use the existing validateAndLog method with the new V2 parameters
        // The purchaseToken serves as the transactionId for iOS
        AppsFlyerLib.shared().validateAndLogInAppPurchase(purchaseDetails: purchaseDetails, purchaseAdditionalDetails: additionalParameters){result, error in
            if result != nil {
                var response: [String: Any] = [:]
                if let resultDict = result as? [String: Any] {
                    response = resultDict
                } else {
                    response["result"] = result
                }
                call.resolve(response)
            } else if error != nil {
                var errorDict: [String: Any] = [:]
                if let nsError = error as NSError? {
                    errorDict["error"] = nsError.localizedDescription
                    errorDict["code"] = nsError.code
                    errorDict["domain"] = nsError.domain
                    // Convert userInfo to JSON-safe values (userInfo may contain non-serializable objects like NSError)
                    errorDict["userInfo"] = nsError.userInfo.jsonSafeRepresentation()
                }
                call.reject("Validation failed", errorDict.jsonStringRepresentation ?? "")
            }
        }
    }
    
    func mapPurchaseType(_ purchaseTypeString: String) -> AFSDKPurchaseType? {
        switch purchaseTypeString {
        case "subscription":
            return    .subscription
            
        case "one_time_purchase":
            return    .oneTimePurchase
            
        default:
            return    nil
        }
    }
}

extension AppsFlyerPlugin{
    private func reportBridgeReady(){
        AppsFlyerAttribution.shared.bridgReady = true
        NotificationCenter.default
            .post(name: Notification.Name.appsflyerBridge, object: nil)
    }
    
    @objc  private func sendLaunch(){
        AppsFlyerLib.shared().start()
    }
    
    @objc func handleUrlOpened(notification: NSNotification) {
        guard let object = notification.object as? [String: Any?] else {
            return
        }
        guard let url =  object["url"] else {
            afLogger(msg: "handleUrlOpened url is nil")
            return
        }
        guard let options =  object["options"] else {
            afLogger(msg: "handleUrlOpened options is nil")
            
            return
        }
        afLogger(msg: "handleUrlOpened with \((url as! URL).absoluteString)")
        AppsFlyerAttribution.shared
            .handleOpenUrl(
                open: url as! URL,
                options: options as! [UIApplication.OpenURLOptionsKey: Any]
            )
        
    }
    
    @objc func handleUniversalLink(notification: NSNotification) {
        guard let object = notification.object as? [String: Any?] else {
            return
        }
        let user = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
        guard let url = object["url"] else {
            afLogger(msg: "handleUrlOpened options is url")
            return
        }
        user.webpageURL = (url as! URL)
        afLogger(
            msg: "handleUniversalLink with \(user.webpageURL?.absoluteString ?? "null")"
        )
        AppsFlyerAttribution.shared.continueUserActivity(userActivity: user)
        
    }
    
}

extension AppsFlyerPlugin : AppsFlyerLibDelegate {
    public func onConversionDataSuccess(_ conversionInfo: [AnyHashable : Any]) {
        let json : [String: Any] = [
            "callbackName":"onConversionDataSuccess",
            "data":conversionInfo
        ]
        self.notifyListeners(AppsFlyerConstants.CONVERSION_CALLBACK, data: json)
        
    }
    
    public func onConversionDataFail(_ error: Error) {
        let json : [String: Any] = [
            "callbackName":"onConversionDataFail",
            "status":error.localizedDescription
        ]
        self.notifyListeners(AppsFlyerConstants.CONVERSION_CALLBACK, data: json)
    }
    public func onAppOpenAttribution(_ attributionData: [AnyHashable : Any]) {
        let json : [String: Any] = [
            "callbackName":"onAppOpenAttribution",
            "data":attributionData
        ]
        self.notifyListeners(AppsFlyerConstants.OAOA_CALLBACK, data: json)
    }
    
    public func onAppOpenAttributionFailure(_ error: Error) {
        let json : [String: Any] = [
            "callbackName":"onAppOpenAttributionFailure",
            "status":error.localizedDescription
        ]
        self.notifyListeners(AppsFlyerConstants.OAOA_CALLBACK, data: json)
        
    }
    
}


// Mark -
extension AppsFlyerPlugin : DeepLinkDelegate{
    public func didResolveDeepLink(_ result: DeepLinkResult) {
        var json : [String: Any] = [:]
        
        switch result.status {
        case .notFound :
            json["status"] = "NOT_FOUND"
        case .failure :
            json["status"] = "FAILURE"
        case .found :
            json["status"] = "FOUND"
        }
        
        if  result.error != nil {
            json["error"] = result.error!.localizedDescription
        }
        if  result.deepLink != nil {
            var deepLinkDic = result.deepLink!.clickEvent
            deepLinkDic["is_deferred"] = result.deepLink!.isDeferred
            json["deepLink"] = deepLinkDic
        }
        self.notifyListeners(AppsFlyerConstants.UDL_CALLBACK, data: json)
    }
    
}

extension AppsFlyerPlugin{
    private func afLogger(msg : String){
        NSLog ("AppsFlyer [Debug][Capacitor]: \(msg)");
    }
}
