import Foundation
import UserNotifications
import Reteno

@objc(RetenoSdk)
open class RetenoSdk: RCTEventEmitter {

    private static let autoOpenLinksKey = "RetenoAutoOpenLinks"

    private static var sdkInitialized = false
    private static var fcmBridgeInstalled = false
    private static var fcmTokenObserver: NSObjectProtocol?

    /// Set to `true` once `delayedStart()` has registered Reteno as the notification-center delegate
    /// from the AppDelegate. When set, JS `initialize()` finishes startup via `Reteno.delayedSetup(...)`
    /// — which replays the push that cold-launched the app — instead of `Reteno.start(...)`.
    private static var delayedStartCalled = false

    private static var autoOpenLinks: Bool {
        get {
            if UserDefaults.standard.object(forKey: autoOpenLinksKey) == nil {
                return true // default value
            }
            return UserDefaults.standard.bool(forKey: autoOpenLinksKey)
        }
        set {
            UserDefaults.standard.set(newValue, forKey: autoOpenLinksKey)
        }
    }

    override init() {
        super.init()
        EventEmitter.sharedInstance.registerEventEmitter(externalEventEmitter: self)

        // Listen for link events posted via NotificationCenter
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleLinkReceived(_:)),
            name: NSNotification.Name("RetenoLinkReceived"),
            object: nil
        )
    }

    /// Registers Reteno as the `UNUserNotificationCenter` delegate early and starts collecting the
    /// push response that cold-launched the app. Requires NO apiKey/config and is safe to call
    /// repeatedly.
    ///
    /// Call this from `application(_:didFinishLaunchingWithOptions:)`, BEFORE starting React Native,
    /// to support push-triggered in-app messages on a cold start:
    /// ```swift
    /// func application(_ application: UIApplication, didFinishLaunchingWithOptions ...) -> Bool {
    ///     FirebaseApp.configure()
    ///     RetenoSdk.delayedStart()
    ///     ...
    /// }
    /// ```
    /// iOS requires the notification-center delegate to be set before `didFinishLaunchingWithOptions`
    /// returns; otherwise the response that cold-launched the app is never delivered and the linked
    /// in-app never shows. The RN module's own `init()` runs too late (lazy instantiation), and
    /// `Reteno.start()` registers the delegate only inside an async callback. `Reteno.delayedStart()`
    /// sets it synchronously; JS `initialize()` then completes startup via `Reteno.delayedSetup(...)`,
    /// which replays the collected push and presents its in-app.
    @objc public static func delayedStart() {
        guard !sdkInitialized, !delayedStartCalled else { return }
        Reteno.delayedStart()
        delayedStartCalled = true
    }

    private func setupRetenoCallbacks() {
        Reteno.addLinkHandler { linkInfo in
            EventEmitter.sharedInstance.dispatch(
                name: "reteno-in-app-custom-data-received",
                body: ["customData": linkInfo.customData, "url": linkInfo.url?.absoluteString as Any]
            )
            if RetenoSdk.autoOpenLinks, let url = linkInfo.url {
                UIApplication.shared.open(url)
            }
        }

        Reteno.userNotificationService.didReceiveNotificationUserInfo = { userInfo in
            EventEmitter.sharedInstance.dispatch(name: "reteno-push-received", body: userInfo)
        }

        Reteno.userNotificationService.didReceiveNotificationResponseHandler = { response in
            EventEmitter.sharedInstance.dispatch(name: "reteno-push-clicked", body: response.notification.request.content.userInfo)
        }

        Reteno.userNotificationService.notificationActionHandler = { userInfo, action in
            let actionId = action.actionId
            let customData = action.customData
            let actionLink = action.link
            EventEmitter.sharedInstance.dispatch(name: "reteno-push-button-clicked", body: ["userInfo": userInfo, "actionId": actionId, "customData": customData as Any, "actionLink": actionLink as Any])
        }
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    @objc private func handleLinkReceived(_ notification: Notification) {
        guard let userInfo = notification.userInfo else { return }
        EventEmitter.sharedInstance.dispatch(
            name: "reteno-in-app-custom-data-received",
            body: userInfo
        )
    }
    
    @objc open override func supportedEvents() -> [String] {
        return EventEmitter.sharedInstance.allEvents;
    }

    @objc(initializeEventHandler:withRejecter:)
    func initializeEventHandler(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
        EventEmitter.sharedInstance.setInitialized()
        resolve(true)
    }

    @objc(initialize:withResolver:withRejecter:)
    func initialize(payload: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
        if RetenoSdk.sdkInitialized {
            resolve(true)
            return
        }

        let apiKey = (payload["apiKey"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
        guard let apiKey = apiKey, !apiKey.isEmpty else {
            reject("100", "Missing argument: apiKey", nil)
            return
        }

        let pauseInAppMessages = (payload["pauseInAppMessages"] as? Bool) ?? false
        let isDebugMode = (payload["isDebugMode"] as? Bool) ?? false

        let lifecycleOptionsInput = payload["lifecycleTrackingOptions"]
        let lifecycleOptions = parseLifecycleTrackingOptions(lifecycleOptionsInput)
        if lifecycleOptionsInput != nil && lifecycleOptions == nil {
            reject(
                "100",
                "Invalid argument: lifecycleTrackingOptions. Expected 'ALL', 'NONE', or lifecycle options object.",
                nil
            )
            return
        }

        let lifecycleAppEnabled = lifecycleOptions?.appLifecycleEnabled ?? true
        let lifecycleForegroundEnabled = lifecycleOptions?.foregroundLifecycleEnabled ?? false
        let lifecyclePushEnabled = lifecycleOptions?.pushSubscriptionEnabled ?? true
        let lifecycleSessionStartEnabled = lifecycleOptions?.sessionStartEventsEnabled ?? true
        let lifecycleSessionEndEnabled = lifecycleOptions?.sessionEndEventsEnabled ?? false

        let sessionDurationSeconds: TimeInterval = {
            if let seconds = payload["sessionDurationSeconds"] as? Double, seconds > 0 {
                return seconds
            }
            if let seconds = payload["sessionDurationSeconds"] as? Int, seconds > 0 {
                return Double(seconds)
            }
            return RetenoSessionConfiguration.default.sessionDuration
        }()

        let deviceTokenMode: DeviceTokenHandlingMode = {
            let raw = (payload["iosDeviceTokenHandlingMode"] as? String)?.lowercased()
            return raw == "manual" ? .manual : .automatic
        }()

        let configuration = RetenoConfiguration(
            isAutomaticScreenReportingEnabled: false,
            isAutomaticAppLifecycleReportingEnabled: lifecycleAppEnabled,
            isApplicationForegroundLifecycleReportingEnabled: lifecycleForegroundEnabled,
            isAutomaticPushSubsriptionReportingEnabled: lifecyclePushEnabled,
            sessionConfiguration: RetenoSessionConfiguration(
                sessionDuration: sessionDurationSeconds,
                isSessionStartReportingEnabled: lifecycleSessionStartEnabled,
                isSessionEndReportingEnabled: lifecycleSessionEndEnabled
            ),
            isPausedInAppMessages: pauseInAppMessages,
            inAppMessagesPauseBehaviour: .postponeInApps,
            isDebugMode: isDebugMode,
            deviceTokenHandlingMode: deviceTokenMode
        )

        // When the AppDelegate called RetenoSdk.delayedStart(), finish the two-phase init so the
        // push collected during cold start is replayed and its in-app is presented. Otherwise
        // (no AppDelegate hook) start normally — everything works except cold-start push → in-app.
        if RetenoSdk.delayedStartCalled {
            Reteno.delayedSetup(apiKey: apiKey, configuration: configuration)
        } else {
            Reteno.start(apiKey: apiKey, configuration: configuration)
        }
        setupRetenoCallbacks()
        if deviceTokenMode == .manual {
            RetenoSdk.installFCMBridgeIfAvailable()
        }
        RetenoSdk.sdkInitialized = true
        resolve(true)
    }

    private func parseLifecycleTrackingOptions(_ value: Any?) -> (
        appLifecycleEnabled: Bool,
        foregroundLifecycleEnabled: Bool,
        pushSubscriptionEnabled: Bool,
        sessionStartEventsEnabled: Bool,
        sessionEndEventsEnabled: Bool
    )? {
        guard let value else { return nil }

        if let optionString = value as? String {
            let normalized = optionString.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
            if normalized == "ALL" {
                return (true, true, true, true, true)
            }
            if normalized == "NONE" {
                return (false, false, false, false, false)
            }
            return nil
        }

        let payload: [String: Any]
        if let dict = value as? [String: Any] {
            payload = dict
        } else if let dict = value as? NSDictionary, let casted = dict as? [String: Any] {
            payload = casted
        } else {
            return nil
        }

        let appLifecycleEnabled = payload["appLifecycleEnabled"] as? Bool ?? true
        let foregroundLifecycleEnabled = payload["foregroundLifecycleEnabled"] as? Bool ?? false
        let pushSubscriptionEnabled = payload["pushSubscriptionEnabled"] as? Bool ?? true
        let legacySessionEventsEnabled = payload["sessionEventsEnabled"] as? Bool ?? true
        let sessionStartEventsEnabled = payload["sessionStartEventsEnabled"] as? Bool ?? legacySessionEventsEnabled

        let sessionEndEventsEnabled: Bool
        if let explicit = payload["sessionEndEventsEnabled"] as? Bool {
            sessionEndEventsEnabled = explicit
        } else if payload["sessionEventsEnabled"] != nil {
            sessionEndEventsEnabled = legacySessionEventsEnabled
        } else {
            sessionEndEventsEnabled = false
        }

        return (
            appLifecycleEnabled,
            foregroundLifecycleEnabled,
            pushSubscriptionEnabled,
            sessionStartEventsEnabled,
            sessionEndEventsEnabled
        )
    }

    @objc(setAutoOpenLinks:withResolver:withRejecter:)
    func setAutoOpenLinks(enabled: Bool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
        RetenoSdk.autoOpenLinks = enabled
        resolve(true)
    }

    @objc(getAutoOpenLinks:withRejecter:)
    func getAutoOpenLinks(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
        resolve(RetenoSdk.autoOpenLinks)
    }

    @objc(setDeviceToken:withResolver:withRejecter:)
    func setDeviceToken(deviceToken: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
        Reteno.userNotificationService.processRemoteNotificationsToken(deviceToken)
        resolve(true)
    }
    
    @objc(setUserAttributes:withResolver:withRejecter:)
    func setUserAttributes(payload: NSDictionary, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
        let externalUserId = payload["externalUserId"] as? String;

        do {
            let requestPayload = try RetenoUserAttributes.buildSetUserAttributesPayload(payload: payload);
            Reteno.updateUserAttributes(
                externalUserId: externalUserId,
                userAttributes: requestPayload.userAttributes,
                subscriptionKeys: requestPayload.subscriptionKeys,
                groupNamesInclude: requestPayload.groupNamesInclude,
                groupNamesExclude: requestPayload.groupNamesExclude
            );
            let res:[String:Bool] = ["success":true];

            resolve(res);
        } catch {
            reject("100", "Reteno iOS SDK Error", error);
        }
    }

    @objc(setMultiAccountUserAttributes:withResolver:withRejecter:)
    func setMultiAccountUserAttributes(payload: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
        let externalUserId = payload["externalUserId"] as? String

        guard let externalUserId = externalUserId, !externalUserId.isEmpty else {
            reject("100", "Missing argument: externalUserId", nil)
            return
        }

        do {
            let requestPayload = try RetenoUserAttributes.buildSetUserAttributesPayload(payload: payload)
            Reteno.updateMultiAccountUserAttributes(
                externalUserId: externalUserId,
                userAttributes: requestPayload.userAttributes,
                subscriptionKeys: requestPayload.subscriptionKeys,
                groupNamesInclude: requestPayload.groupNamesInclude,
                groupNamesExclude: requestPayload.groupNamesExclude,
                accountSuffix: externalUserId
            )
            resolve(["success": true])
        } catch {
            reject("100", "Reteno iOS SDK Error", error)
        }
    }
    
    @objc(getInitialNotification:withRejecter:)
    func getInitialNotification(_ resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
        var initialNotif: Any? = nil;
        let remoteUserInfo = bridge.launchOptions?[UIApplication.LaunchOptionsKey.remoteNotification];
        if (remoteUserInfo != nil) {
            initialNotif = remoteUserInfo;
        }
        if (initialNotif != nil) {
            resolve(initialNotif);
        } else {
            resolve(nil);
        }
    }
    
    @objc(logEvent:withResolver:withRejecter:)
    func logEvent(payload: NSDictionary, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
        do {
            let requestPayload = try RetenoEvent.buildEventPayload(payload: payload);
            Reteno.logEvent(
                eventTypeKey: requestPayload.eventName,
                date: requestPayload.date,
                parameters: requestPayload.parameters,
                forcePush: requestPayload.forcePush
            );
            
            let res:[String:Bool] = ["success":true];
            
            resolve(res);
        } catch {
            reject("100", "Reteno iOS SDK Error", error);
        }
    }
    
    @objc(registerForRemoteNotifications)
    func registerForRemoteNotifications() -> Void {
        // Shows the native iOS notification permission prompt (.sound, .alert, .badge).
        Reteno.userNotificationService.registerForRemoteNotifications(with: [.sound, .alert, .badge], application: UIApplication.shared)
    }

    // Installs an FCM token bridge using the ObjC runtime — no Firebase import required.
    // Subscribes to FIRMessagingRegistrationTokenRefreshedNotification and reads
    // FIRMessaging.messaging().FCMToken via KVC. Does NOT replace Messaging.delegate
    // so @react-native-firebase/messaging internals are not affected.
    private static func installFCMBridgeIfAvailable() {
        guard !fcmBridgeInstalled else { return }
        guard NSClassFromString("FIRMessaging") != nil else {
            NSLog("[Reteno] iosDeviceTokenHandlingMode='manual': FIRMessaging not found at runtime. Provide the push token via setDeviceToken().")
            return
        }
        fcmBridgeInstalled = true
        fcmTokenObserver = NotificationCenter.default.addObserver(
            forName: Notification.Name("com.firebase.messaging.notif.fcm-token-refreshed"),
            object: nil,
            queue: .main
        ) { _ in
            RetenoSdk.forwardFCMTokenToReteno()
        }
        // Forward any already-cached token on subsequent launches.
        forwardFCMTokenToReteno()
    }

    private static func forwardFCMTokenToReteno() {
        guard
            let fmClass = NSClassFromString("FIRMessaging") as? NSObject.Type,
            let messaging = fmClass.perform(NSSelectorFromString("messaging"))?.takeUnretainedValue() as? NSObject,
            let token = messaging.value(forKey: "FCMToken") as? String,
            !token.isEmpty
        else { return }
        Reteno.userNotificationService.processRemoteNotificationsToken(token)
    }
    
    @objc(setAnonymousUserAttributes:withResolver:withRejecter:)
    func setAnonymousUserAttributes(payload: NSDictionary, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
        do {
            let anonymousUser = try RetenoUserAttributes.buildSetAnonymousUserAttributesPayload(payload: payload)

            Reteno.updateAnonymousUserAttributes(userAttributes: anonymousUser)
            resolve(true)
        } catch {
            reject("100", "Reteno iOS SDK setAnonymousUserAttributes Error", error);
        }
    }
    
    @objc(pauseInAppMessages:withResolver:withRejecter:)
    func pauseInAppMessages(isPaused: Bool, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
        Reteno.pauseInAppMessages(isPaused: isPaused)
        resolve(true)
    }

    @objc(setInAppMessagesPauseBehaviour:withResolver:withRejecter:)
    func setInAppMessagesPauseBehaviour(behaviour: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
        let normalized = behaviour.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
        switch normalized {
        case "SKIP_IN_APPS":
            Reteno.setInAppMessagesPauseBehaviour(pauseBehaviour: .skipInApps)
            resolve(true)
        case "POSTPONE_IN_APPS":
            Reteno.setInAppMessagesPauseBehaviour(pauseBehaviour: .postponeInApps)
            resolve(true)
        default:
            reject("100", "Invalid argument: behaviour must be 'SKIP_IN_APPS' or 'POSTPONE_IN_APPS'", nil)
        }
    }
    
    @objc(setInAppLifecycleCallback)
    func setInAppLifecycleCallback() {
        Reteno.addInAppStatusHandler { inAppMessageStatus in
            switch inAppMessageStatus {
            case .inAppShouldBeDisplayed:
                EventEmitter.sharedInstance.dispatch(name: "reteno-before-in-app-display", body: nil)
            case .inAppIsDisplayed:
                EventEmitter.sharedInstance.dispatch(name: "reteno-on-in-app-display", body: nil)
            case .inAppShouldBeClosed(let action):
                EventEmitter.sharedInstance.dispatch(name: "reteno-before-in-app-close", body: [
                    "closeAction": RetenoSdk.closeActionName(action),
                    "isCloseButtonClicked": action.isCloseButtonClicked,
                    "isButtonClicked": action.isButtonClicked,
                    "isOpenUrlClicked": action.isOpenUrlClicked
                ])
            case .inAppIsClosed(let action):
                EventEmitter.sharedInstance.dispatch(name: "reteno-after-in-app-close", body: [
                    "closeAction": RetenoSdk.closeActionName(action),
                    "isCloseButtonClicked": action.isCloseButtonClicked,
                    "isButtonClicked": action.isButtonClicked,
                    "isOpenUrlClicked": action.isOpenUrlClicked
                ])
            case .inAppReceivedError(let error):
                EventEmitter.sharedInstance.dispatch(name: "reteno-on-in-app-error", body: ["errorMessage": error])
            }
        }
    }

    private static func closeActionName(_ action: InAppMessageAction) -> String {
        if action.isCloseButtonClicked { return "CLOSE_BUTTON" }
        if action.isButtonClicked { return "BUTTON" }
        if action.isOpenUrlClicked { return "OPEN_URL" }
        return "UNKNOWN"
    }

    @objc(getRecommendations:withResolver:withRejecter:)
    func getRecommendations(payload: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        guard let recomVariantId = payload["recomVariantId"] as? String,
              let productIds = payload["productIds"] as? [String],
              let categoryId = payload["categoryId"] as? String,
              let filters = payload["filters"] as? [NSDictionary],
              let fields = payload["fields"] as? [String] else {
            let error = NSError(domain: "RetenoSdk", code: 100, userInfo: [NSLocalizedDescriptionKey: "Invalid payload"])
            reject("100", "Reteno iOS SDK Error: Invalid payload", error)
            return
        }

        var recomFilters: [RecomFilter]? = nil
        if let filters = filters as? [[String: Any]] {
            recomFilters = filters.compactMap { dict in
                guard let name = dict["name"] as? String, let values = dict["values"] as? [String] else {
                    return nil
                }
                return RecomFilter(name: name, values: values)
            }
        }
        
        Reteno.recommendations().getRecoms(recomVariantId: recomVariantId, productIds: productIds, categoryId: categoryId, filters: recomFilters, fields: fields) { (result: Result<[Recommendation], Error>) in
            
            switch result {
            case .success(let recommendations):
                let serializedRecommendations = recommendations.map { recommendation in
                    return [
                        "productId": recommendation.productId,
                        "name": recommendation.name,
                        "description": recommendation.description ?? "",
                        "imageUrl": recommendation.imageUrl?.absoluteString ?? "",
                        "price": recommendation.price
                    ]
                }
                resolve(serializedRecommendations)
                
            case .failure(let error):
                reject("100", "Reteno iOS SDK getRecommendations Error", error)
            }
        }
    }
    
    @objc(logRecommendationEvent:withResolver:withRejecter:)
    func logRecommendationEvent(payload: NSDictionary, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
        
        guard let recomVariantId = payload["recomVariantId"] as? String,
              let impressions = payload["impressions"] as? [[String: Any]],
              let clicks = payload["clicks"] as? [[String: Any]],
              let forcePush = payload["forcePush"] as? Bool else {
            let error = NSError(domain: "InvalidPayload", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid payload"])
            reject("100", "Reteno iOS SDK logRecommendationEvent Error", error)
            return
        }
        
        var impressionEvents: [RecomEvent] = []
        var clickEvents: [RecomEvent] = []
        
        for impression in impressions {
            let productId = impression["productId"] as? String
            
            impressionEvents.append(RecomEvent(date: Date(), productId: productId ?? ""))
        }
        
        for click in clicks {
            let productId = click["productId"] as? String
            
            clickEvents.append(RecomEvent(date: Date(), productId: productId ?? ""))
        }
        
        Reteno.recommendations().logEvent(recomVariantId: recomVariantId, impressions: impressionEvents, clicks: clickEvents, forcePush: forcePush)
        
        let res: [String: Bool] = ["success": true]
        resolve(res)
    }
    
    @objc(getAppInboxMessages:withResolver:withRejecter:)
    func getAppInboxMessages(payload: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        let page = payload["page"] as? Int
        let pageSize = payload["pageSize"] as? Int
        let statusString = payload["status"] as? String
      
      let status: AppInboxMessagesStatus? = {
              switch statusString?.uppercased() {
              case "OPENED":
                  return .opened
              case "UNOPENED":
                  return .unopened
              default:
                  return nil
              }
          }()
        
        Reteno.inbox().downloadMessages(page: page, pageSize: pageSize, status: status) { result in
            switch result {
            case .success(let response):
                let messages = response.messages.map { message in
                    return [
                        "id": message.id,
                        "createdDate": message.createdDate?.timeIntervalSince1970 as Any,
                        "title": message.title as Any,
                        "content": message.content as Any,
                        "imageURL": message.imageURL?.absoluteString as Any,
                        "linkURL": message.linkURL?.absoluteString as Any,
                        "isNew": message.isNew,
                    ]
                }
                resolve(["messages": messages, "totalPages": response.totalPages as Any])
                
            case .failure(let error):
                reject("100", "Reteno iOS SDK downloadMessages Error", error)
            }
        }
    }
    
    @objc(onUnreadMessagesCountChanged:withRejecter:)
    func onUnreadMessagesCountChanged(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        Reteno.inbox().onUnreadMessagesCountChanged = { count in
            EventEmitter.sharedInstance.dispatch(name: "reteno-unread-messages-count", body: ["count": count])
        }
        resolve(nil)
    }

    @objc(unsubscribeMessagesCountChanged:withRejecter:)
    func unsubscribeMessagesCountChanged(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        Reteno.inbox().onUnreadMessagesCountChanged = nil
        resolve(nil)
    }

    @objc(unsubscribeAllMessagesCountChanged:withRejecter:)
    func unsubscribeAllMessagesCountChanged(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        Reteno.inbox().onUnreadMessagesCountChanged = nil
        resolve(nil)
    }
    
    @objc(markAsOpened:withResolver:withRejecter:)
        func markAsOpened(messageIds: [String], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
            Reteno.inbox().markAsOpened(messageIds: messageIds) { result in
                switch result {
                case .success:
                    resolve(true)
                case .failure(let error):
                    reject("100", "Reteno iOS SDK markAsOpened Error", error)
                }
            }
        }
    
    @objc(markAllAsOpened:withRejecter:)
        func markAllAsOpened(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
            Reteno.inbox().markAllAsOpened { result in
                switch result {
                case .success:
                    resolve(true)
                case .failure(let error):
                    reject("100", "Reteno iOS SDK markAllAsOpened Error", error)
                }
            }
        }
    
    @objc(getAppInboxMessagesCount:withRejecter:)

        func getAppInboxMessagesCount(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
            Reteno.inbox().getUnreadMessagesCount { result in
                switch result {
                case .success(let unreadCount):
                    resolve(unreadCount)
                case .failure(let error):
                    reject("100", "Reteno iOS SDK getAppInboxMessagesCount Error", error)
                }
            }
        }

      @objc func logEcomEventProductViewed(_ payload: [String: Any], resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        guard let data = RetenoEcomEvent.buildProductDataFromPayload(payload) else {
            reject("Payload Error", "Payload cannot be null", nil)
            return
        }
        
        do {
            Reteno.ecommerce().logEvent(type: .productViewed(product: data.product, currencyCode: data.currencyCode),
                                        date: Date(),
                                        forcePush: true)
            resolve(["success": true])
        } catch {
            reject("Reteno iOS SDK Error", error.localizedDescription, error)
        }
    }
    
    @objc func logEcomEventProductCategoryViewed(_ payload: [String: Any], resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        guard let category = RetenoEcomEvent.buildProductCategoryDataFromPayload(payload) else {
            reject("Payload Error", "Payload cannot be null", nil)
            return
        }
        
        do {
            Reteno.ecommerce().logEvent(type: .productCategoryViewed(category: category),
                                        date: Date(),
                                        forcePush: true)
            resolve(["success": true])
        } catch {
            reject("Reteno iOS SDK Error", error.localizedDescription, error)
        }
    }
    
    @objc func logEcomEventProductAddedToWishlist(_ payload: [String: Any], resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        guard let data = RetenoEcomEvent.buildProductDataFromPayload(payload) else {
            reject("Payload Error", "Payload cannot be null", nil)
            return
        }
        
        do {
            Reteno.ecommerce().logEvent(type: .productAddedToWishlist(product: data.product, currencyCode: data.currencyCode),
                                        date: Date(),
                                        forcePush: true)
            resolve(["success": true])
        } catch {
            reject("Reteno iOS SDK Error", error.localizedDescription, error)
        }
    }
    
    @objc func logEcomEventCartUpdated(_ payload: [String: Any], resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        guard let data = RetenoEcomEvent.buildCartUpdatedDataFromPayload(payload) else {
            reject("Payload Error", "Payload cannot be null", nil)
            return
        }
        
        do {
            Reteno.ecommerce().logEvent(type: .cartUpdated(
                cartId: data.cartId,
                products: data.products,
                currencyCode: data.currencyCode
            ),
                                        date: Date(),
                                        forcePush: true)
            resolve(["success": true])
        } catch {
            reject("Reteno iOS SDK Error", error.localizedDescription, error)
        }
    }
    
    
    @objc func logEcomEventOrderCreated(_ payload: [String: Any], resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        guard let data = RetenoEcomEvent.buildOrderDataFromPayload(payload) else {
            reject("Payload Error", "Payload cannot be null", nil)
            return
        }
        do {
            Reteno.ecommerce().logEvent(type: .orderCreated(order: data.order, currencyCode: data.currencyCode),
                                        date: Date(),
                                        forcePush: true)
           
            
            resolve(["success": true])
        } catch {
            reject("Reteno iOS SDK Error", error.localizedDescription, error)
        }
    }
    
    @objc func logEcomEventOrderUpdated(_ payload: [String: Any], resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        guard let data = RetenoEcomEvent.buildOrderDataFromPayload(payload) else {
            reject("Payload Error", "Payload cannot be null", nil)
            return
        }
        
        do {
            Reteno.ecommerce().logEvent(type: .orderUpdated(order: data.order, currencyCode: data.currencyCode),
                                        date: Date(),
                                        forcePush: true)
            resolve(["success": true])
        } catch {
            reject("Reteno iOS SDK Error", error.localizedDescription, error)
        }
    }
    
    @objc func logEcomEventOrderDelivered(_ payload: [String: Any], resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        guard let externalOrderId = RetenoEcomEvent.buildOrderExternalIdFromPayload(payload) else {
            reject("Payload Error", "Payload cannot be null", nil)
            return
        }
        
        do {
            Reteno.ecommerce().logEvent(type: .orderDelivered(externalOrderId: externalOrderId))
            resolve(["success": true])
        } catch {
            reject("Reteno iOS SDK Error", error.localizedDescription, error)
        }
    }
    
    @objc func logEcomEventOrderCancelled(_ payload: [String: Any], resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        guard let externalOrderId = RetenoEcomEvent.buildOrderExternalIdFromPayload(payload) else {
            reject("Payload Error", "Payload cannot be null", nil)
            return
        }
        
        do {
            Reteno.ecommerce().logEvent(type: .orderCancelled(externalOrderId: externalOrderId),
                                        date: Date(),
                                        forcePush: true)
            resolve(["success": true])
        } catch {
            reject("Reteno iOS SDK Error", error.localizedDescription, error)
        }
    }
    
    @objc func logEcomEventSearchRequest(_ payload: [String: Any], resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        guard let data = RetenoEcomEvent.buildSearchRequestDataFromPayload(payload) else {
            reject("Payload Error", "Payload cannot be null", nil)
            return
        }
        do {
            Reteno.ecommerce().logEvent(type: .searchRequest(query: data.searchQuery, isFound: data.isFound))
            resolve(["success": true])
        } catch {
            reject("Reteno iOS SDK Error", error.localizedDescription, error)
        }
    }
}
