#import "RCTOneSignalEventEmitter.h"
#import <OneSignalFramework/OneSignalFramework.h>
#import <OneSignalOSCore/OneSignalOSCore-Swift.h>
#import <OneSignalLiveActivities/OneSignalLiveActivities-Swift.h>
#import "RCTOneSignal.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"

// Sentinel string used by the JS side of `trackEvent` to round-trip `null`
// values across the React Native iOS TurboModule bridge. Must match
// IOS_NULL_SENTINEL in src/constants/internal.ts byte-for-byte. The string
// avoids NUL bytes because RN's convertJSIStringToNSString uses
// stringWithUTF8String: which would truncate at the first NUL.
// See SDK-4386.
static NSString *const kOSNullSentinel = @"__OS_RN_NULL_8b3f72d6c1a04f9e__";

// Recursively walks `value`, returning a copy with any string equal to the
// sentinel replaced by `[NSNull null]`. Containers are rebuilt; primitives
// are returned as-is.
static id OSDecodeNullSentinels(id value) {
  if ([value isKindOfClass:[NSDictionary class]]) {
    NSDictionary *dict = (NSDictionary *)value;
    NSMutableDictionary *out = [NSMutableDictionary dictionaryWithCapacity:dict.count];
    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
      out[key] = OSDecodeNullSentinels(obj);
    }];
    return out;
  }
  if ([value isKindOfClass:[NSArray class]]) {
    NSArray *arr = (NSArray *)value;
    NSMutableArray *out = [NSMutableArray arrayWithCapacity:arr.count];
    for (id item in arr) {
      [out addObject:OSDecodeNullSentinels(item)];
    }
    return out;
  }
  if ([value isKindOfClass:[NSString class]] && [(NSString *)value isEqualToString:kOSNullSentinel]) {
    return [NSNull null];
  }
  return value;
}

@interface RCTOneSignalEventEmitter ()
- (void)emitEventWithName:(NSString *)name body:(NSDictionary *)body;
@end

@implementation RCTOneSignalEventEmitter {
  BOOL _hasSetSubscriptionObserver;
  BOOL _hasSetPermissionObserver;
  BOOL _hasSetUserStateObserver;
  BOOL _hasAddedNotificationClickListener;
  BOOL _hasAddedNotificationForegroundLifecycleListener;
  BOOL _hasAddedInAppMessageClickListener;
  BOOL _hasAddedInAppMessageLifecycleListener;
  NSMutableDictionary *_preventDefaultCache;
  NSMutableDictionary *_notificationWillDisplayCache;
}

static RCTOneSignalEventEmitter *_currentInstance = nil;

+ (BOOL)requiresMainQueueSetup {
  return YES;
}

RCT_EXPORT_MODULE(OneSignal)

- (instancetype)init {
  if (self = [super init]) {
    _preventDefaultCache = [NSMutableDictionary new];
    _notificationWillDisplayCache = [NSMutableDictionary new];

    // Clean up previous instance if it exists (handles reload scenario)
    if (_currentInstance != nil && _currentInstance != self) {
      [_currentInstance removeHandlers];
      [_currentInstance removeObservers];
    }
    _currentInstance = self;
  }

  return self;
}

- (void)invalidate {
  [self removeHandlers];
  [self removeObservers];
  [_preventDefaultCache removeAllObjects];
  [_notificationWillDisplayCache removeAllObjects];
  if (_currentInstance == self) {
    _currentInstance = nil;
  }
}

- (NSArray<NSString *> *)processNSError:(NSError *)error {
  if (error.userInfo[@"error"]) {
    return @[ error.userInfo[@"error"] ];
  } else if (error.userInfo[@"returned"]) {
    return @[ error.userInfo[@"returned"] ];
  }

  return @[ error.localizedDescription ];
}

#pragma mark Send Event Methods

- (void)emitEventWithName:(NSString *)name body:(NSDictionary *)body {
  if ([name isEqualToString:OSEventString(PermissionChanged)]) {
    [self emitOnPermissionChanged:body];
  } else if ([name isEqualToString:OSEventString(SubscriptionChanged)]) {
    [self emitOnSubscriptionChanged:body];
  } else if ([name isEqualToString:OSEventString(UserStateChanged)]) {
    [self emitOnUserStateChanged:body];
  } else if ([name isEqualToString:
                        OSEventString(NotificationWillDisplayInForeground)]) {
    [self emitOnNotificationWillDisplay:body];
  } else if ([name isEqualToString:OSEventString(NotificationClicked)]) {
    [self emitOnNotificationClicked:body];
  } else if ([name isEqualToString:OSEventString(InAppMessageClicked)]) {
    [self emitOnInAppMessageClicked:body];
  } else if ([name isEqualToString:OSEventString(InAppMessageWillDisplay)]) {
    [self emitOnInAppMessageWillDisplay:body];
  } else if ([name isEqualToString:OSEventString(InAppMessageDidDisplay)]) {
    [self emitOnInAppMessageDidDisplay:body];
  } else if ([name isEqualToString:OSEventString(InAppMessageWillDismiss)]) {
    [self emitOnInAppMessageWillDismiss:body];
  } else if ([name isEqualToString:OSEventString(InAppMessageDidDismiss)]) {
    [self emitOnInAppMessageDidDismiss:body];
  }
}

+ (void)sendEventWithName:(NSString *)name withBody:(NSDictionary *)body {
  [_currentInstance emitEventWithName:name body:body];
}

#pragma mark Exported Methods

// OneSignal root namespace methods
RCT_EXPORT_METHOD(initialize : (NSString *_Nonnull)appId) {
  [OneSignal initialize:appId withLaunchOptions:NULL];
}

RCT_EXPORT_METHOD(login : (NSString *)externalId) {
  [OneSignal login:externalId];
}

RCT_EXPORT_METHOD(logout) { [OneSignal logout]; }

RCT_EXPORT_METHOD(enterLiveActivity : (NSString *)activityId
                  token : (NSString *)token
                  callback : (RCTResponseSenderBlock)callback) {
  [OneSignal.LiveActivities enter:activityId
      withToken:token
      withSuccess:^(NSDictionary *result) {
        callback(@[ result ]);
      }
      withFailure:^(NSError *error) {
        callback([self processNSError:error]);
      }];
}

RCT_EXPORT_METHOD(exitLiveActivity : (NSString *)activityId
                  callback : (RCTResponseSenderBlock)callback) {
  [OneSignal.LiveActivities exit:activityId
      withSuccess:^(NSDictionary *result) {
        callback(@[ result ]);
      }
      withFailure:^(NSError *error) {
        callback([self processNSError:error]);
      }];
}

RCT_EXPORT_METHOD(setPushToStartToken : (NSString *)activityType
                  token : (NSString *)token) {
#if !TARGET_OS_MACCATALYST
  NSError *err = nil;
  if (@available(iOS 17.2, *)) {
    [OneSignalLiveActivitiesManagerImpl setPushToStartToken:activityType
                                                  withToken:token
                                                      error:&err];
    if (err) {
      [OneSignalLog
          onesignalLog:ONE_S_LL_ERROR
               message:[NSString
                           stringWithFormat:@"activityType must be the name of "
                                            @"your ActivityAttributes struct"]];
    }
  } else {
    [OneSignalLog
        onesignalLog:ONE_S_LL_ERROR
             message:[NSString
                         stringWithFormat:
                             @"cannot setPushToStartToken on iOS < 17.2"]];
  }
#endif
}

RCT_EXPORT_METHOD(removePushToStartToken : (NSString *)activityType) {
#if !TARGET_OS_MACCATALYST
  NSError *err = nil;
  if (@available(iOS 17.2, *)) {
    [OneSignalLiveActivitiesManagerImpl removePushToStartToken:activityType
                                                         error:&err];
    if (err) {
      [OneSignalLog
          onesignalLog:ONE_S_LL_ERROR
               message:[NSString
                           stringWithFormat:@"activityType must be the name of "
                                            @"your ActivityAttributes struct"]];
    }
  } else {
    [OneSignalLog
        onesignalLog:ONE_S_LL_ERROR
             message:[NSString
                         stringWithFormat:
                             @"cannot removePushToStartToken on iOS < 17.2"]];
  }
#endif
}

RCT_EXPORT_METHOD(setupDefaultLiveActivity : (NSDictionary *_Nullable)options) {
#if !TARGET_OS_MACCATALYST
  LiveActivitySetupOptions *laOptions = nil;
  if (options != nil) {
    laOptions = [LiveActivitySetupOptions alloc];
    [laOptions setEnablePushToStart:[options[@"enablePushToStart"] boolValue]];
    [laOptions
        setEnablePushToUpdate:[options[@"enablePushToUpdate"] boolValue]];
  }

  if (@available(iOS 16.1, *)) {
    [OneSignalLiveActivitiesManagerImpl setupDefaultWithOptions:laOptions];
  } else {
    [OneSignalLog
        onesignalLog:ONE_S_LL_ERROR
             message:[NSString stringWithFormat:
                                   @"cannot setupDefault on iOS < 16.1"]];
  }
#endif
}

RCT_EXPORT_METHOD(startDefaultLiveActivity : (NSString *)activityId
                  attributes : (NSDictionary *_Nonnull)attributes
                  content : (NSDictionary *_Nonnull)content) {
#if !TARGET_OS_MACCATALYST
  if (@available(iOS 16.1, *)) {
    [OneSignalLiveActivitiesManagerImpl startDefault:activityId
                                          attributes:attributes
                                             content:content];
  } else {
    [OneSignalLog
        onesignalLog:ONE_S_LL_ERROR
             message:[NSString stringWithFormat:
                                   @"cannot startDefault on iOS < 16.1"]];
  }
#endif
}

RCT_EXPORT_METHOD(setPrivacyConsentGiven : (BOOL)granted) {
  dispatch_async(dispatch_get_main_queue(), ^{
    [OneSignal setConsentGiven:granted];
  });
}

RCT_EXPORT_METHOD(setPrivacyConsentRequired : (BOOL)required) {
  [OneSignal setConsentRequired:required];
}

// OneSignal.Debug namespace methods
RCT_EXPORT_METHOD(setLogLevel : (double)logLevel) {
  [OneSignal.Debug setLogLevel:(ONE_S_LOG_LEVEL)(int)logLevel];
}

RCT_EXPORT_METHOD(setAlertLevel : (double)logLevel) {
  [OneSignal.Debug setAlertLevel:(ONE_S_LOG_LEVEL)(int)logLevel];
}

// OneSignal.InAppMessages namespace methods
RCT_EXPORT_METHOD(getPaused : (RCTPromiseResolveBlock)resolve
                  reject : (RCTPromiseRejectBlock)reject) {
  resolve(@([OneSignal.InAppMessages paused]));
}

RCT_EXPORT_METHOD(paused : (BOOL)pause) {
  [OneSignal.InAppMessages paused:pause];
}

RCT_EXPORT_METHOD(addTrigger : (NSString *)key value : (NSString *)value) {
  [OneSignal.InAppMessages addTrigger:key withValue:value];
}

RCT_EXPORT_METHOD(addTriggers : (NSDictionary *)triggers) {
  [OneSignal.InAppMessages addTriggers:triggers];
}

RCT_EXPORT_METHOD(removeTrigger : (NSString *)key) {
  [OneSignal.InAppMessages removeTrigger:key];
}

RCT_EXPORT_METHOD(removeTriggers : (NSArray *)keys) {
  [OneSignal.InAppMessages removeTriggers:keys];
}

RCT_EXPORT_METHOD(clearTriggers) { [OneSignal.InAppMessages clearTriggers]; }

RCT_EXPORT_METHOD(addInAppMessageClickListener) {
  if (!_hasAddedInAppMessageClickListener) {
    [OneSignal.InAppMessages addClickListener:[RCTOneSignal sharedInstance]];
    _hasAddedInAppMessageClickListener = true;
  }
}

RCT_EXPORT_METHOD(addInAppMessagesLifecycleListener) {
  if (!_hasAddedInAppMessageLifecycleListener) {
    [OneSignal.InAppMessages
        addLifecycleListener:[RCTOneSignal sharedInstance]];
    _hasAddedInAppMessageLifecycleListener = true;
  }
}

// OneSignal.Location namespace methods
RCT_EXPORT_METHOD(isLocationShared : (RCTPromiseResolveBlock)resolve
                  reject : (RCTPromiseRejectBlock)reject) {
  resolve(@([OneSignal.Location isShared]));
}

RCT_EXPORT_METHOD(setLocationShared : (BOOL)shared) {
  [OneSignal.Location setShared:shared];
}

RCT_EXPORT_METHOD(requestLocationPermission) {
  [OneSignal.Location requestPermission];
}

// OneSignal.Notifications namespace methods
RCT_EXPORT_METHOD(hasNotificationPermission : (RCTPromiseResolveBlock)resolve
                  reject : (RCTPromiseRejectBlock)reject) {
  resolve(@([OneSignal.Notifications permission]));
}

RCT_EXPORT_METHOD(canRequestNotificationPermission : (RCTPromiseResolveBlock)
                      resolve
                  reject : (RCTPromiseRejectBlock)reject) {
  resolve(@([OneSignal.Notifications canRequestPermission]));
}

RCT_EXPORT_METHOD(requestNotificationPermission : (BOOL)fallbackToSettings
                  resolve : (RCTPromiseResolveBlock)resolve
                  reject : (RCTPromiseRejectBlock)reject) {
  [OneSignal.Notifications
       requestPermission:^(BOOL accepted) {
         resolve(@(accepted));
       }
      fallbackToSettings:fallbackToSettings];
}

RCT_EXPORT_METHOD(registerForProvisionalAuthorization : (RCTResponseSenderBlock)
                      callback) {
  [OneSignal.Notifications
      registerForProvisionalAuthorization:^(BOOL accepted) {
        callback(@[ @(accepted) ]);
      }];
}

RCT_EXPORT_METHOD(addPermissionObserver) {
  if (!_hasSetPermissionObserver) {
    [OneSignal.Notifications
        addPermissionObserver:[RCTOneSignal sharedInstance]];
    _hasSetPermissionObserver = true;
  }
}

- (void)removePermissionObserver {
  if (_hasSetPermissionObserver) {
    [OneSignal.Notifications
        removePermissionObserver:[RCTOneSignal sharedInstance]];
    _hasSetPermissionObserver = false;
  }
}

RCT_EXPORT_METHOD(permissionNative : (RCTPromiseResolveBlock)resolve
                  reject : (RCTPromiseRejectBlock)reject) {
  resolve(@([OneSignal.Notifications permissionNative]));
}

RCT_EXPORT_METHOD(addNotificationClickListener) {
  if (!_hasAddedNotificationClickListener) {
    [OneSignal.Notifications addClickListener:[RCTOneSignal sharedInstance]];
    _hasAddedNotificationClickListener = true;
  }
}

RCT_EXPORT_METHOD(addNotificationForegroundLifecycleListener) {
  if (!_hasAddedNotificationForegroundLifecycleListener) {
    [OneSignal.Notifications addForegroundLifecycleListener:self];
    _hasAddedNotificationForegroundLifecycleListener = true;
  }
}

- (void)onWillDisplayNotification:(OSNotificationWillDisplayEvent *)event {
  _notificationWillDisplayCache[event.notification.notificationId] = event;
  [event preventDefault];
  [RCTOneSignalEventEmitter
      sendEventWithName:@"OneSignal-notificationWillDisplayInForeground"
               withBody:[event.notification jsonRepresentation]];
}

RCT_EXPORT_METHOD(preventDefault : (NSString *)notificationId) {
  OSNotificationWillDisplayEvent *event =
      _notificationWillDisplayCache[notificationId];
  if (!event) {
    [OneSignalLog
        onesignalLog:ONE_S_LL_ERROR
             message:[NSString
                         stringWithFormat:
                             @"OneSignal (objc): could not find notification "
                             @"will display event for notification with id: %@",
                             notificationId]];
    return;
  }
  _preventDefaultCache[event.notification.notificationId] = event;
  [event preventDefault];
}

RCT_EXPORT_METHOD(clearAllNotifications) { [OneSignal.Notifications clearAll]; }

// Android-only stubs
RCT_EXPORT_METHOD(removeNotification : (double)notificationId) {
  // Android only, no-op on iOS
}

RCT_EXPORT_METHOD(removeGroupedNotifications : (NSString *)groupId) {
  // Android only, no-op on iOS
}

// OneSignal.Session namespace methods
RCT_EXPORT_METHOD(addOutcome : (NSString *)name) {
  [OneSignal.Session addOutcome:name];
}

RCT_EXPORT_METHOD(addUniqueOutcome : (NSString *)name) {
  [OneSignal.Session addUniqueOutcome:name];
}

RCT_EXPORT_METHOD(addOutcomeWithValue : (NSString *)name
                  value : (double)value) {
  [OneSignal.Session addOutcomeWithValue:name value:@(value)];
}

// OneSignal.User namespace methods
RCT_EXPORT_METHOD(addUserStateObserver) {
  if (!_hasSetUserStateObserver) {
    [OneSignal.User addObserver:[RCTOneSignal sharedInstance]];
    _hasSetUserStateObserver = true;
  }
}

RCT_EXPORT_METHOD(addPushSubscriptionObserver) {
  if (!_hasSetSubscriptionObserver) {
    [OneSignal.User.pushSubscription addObserver:[RCTOneSignal sharedInstance]];
    _hasSetSubscriptionObserver = true;
  }
}

- (void)removePushSubscriptionObserver {
  if (_hasSetSubscriptionObserver) {
    [OneSignal.User.pushSubscription
        removeObserver:[RCTOneSignal sharedInstance]];
    _hasSetSubscriptionObserver = false;
  }
}

RCT_EXPORT_METHOD(setLanguage : (NSString *)language) {
  [OneSignal.User setLanguage:language];
}

RCT_EXPORT_METHOD(addEmail : (NSString *)email) {
  [OneSignal.User addEmail:email];
}

RCT_EXPORT_METHOD(removeEmail : (NSString *)email) {
  [OneSignal.User removeEmail:email];
}

RCT_EXPORT_METHOD(addSms : (NSString *)smsNumber) {
  [OneSignal.User addSms:smsNumber];
}

RCT_EXPORT_METHOD(removeSms : (NSString *)smsNumber) {
  [OneSignal.User removeSms:smsNumber];
}

RCT_EXPORT_METHOD(addTag : (NSString *)key value : (id)value) {
  [OneSignal.User addTagWithKey:key value:value];
}

RCT_EXPORT_METHOD(addTags : (NSDictionary *)tags) {
  [OneSignal.User addTags:tags];
}

RCT_EXPORT_METHOD(removeTag : (NSString *)key) {
  [OneSignal.User removeTag:key];
}

RCT_EXPORT_METHOD(removeTags : (NSArray *)keys) {
  [OneSignal.User removeTags:keys];
}

RCT_EXPORT_METHOD(getTags : (RCTPromiseResolveBlock)resolve
                  reject : (RCTPromiseRejectBlock)reject) {
  NSDictionary<NSString *, NSString *> *tags = [OneSignal.User getTags];
  resolve(tags);
}

RCT_EXPORT_METHOD(getOnesignalId : (RCTPromiseResolveBlock)resolve
                  reject : (RCTPromiseRejectBlock)reject) {
  NSString *onesignalId = OneSignal.User.onesignalId;

  if (onesignalId == nil || [onesignalId length] == 0) {
    resolve([NSNull null]);
  } else {
    resolve(onesignalId);
  }
}

RCT_EXPORT_METHOD(getExternalId : (RCTPromiseResolveBlock)resolve
                  reject : (RCTPromiseRejectBlock)reject) {
  NSString *externalId = OneSignal.User.externalId;

  if (externalId == nil || [externalId length] == 0) {
    resolve([NSNull null]);
  } else {
    resolve(externalId);
  }
}

RCT_EXPORT_METHOD(addAlias : (NSString *)label id : (NSString *)id) {
  [OneSignal.User addAliasWithLabel:label id:id];
}

RCT_EXPORT_METHOD(removeAlias : (NSString *)label) {
  [OneSignal.User removeAlias:label];
}

RCT_EXPORT_METHOD(addAliases : (NSDictionary *)aliases) {
  [OneSignal.User addAliases:aliases];
}

RCT_EXPORT_METHOD(removeAliases : (NSArray *)labels) {
  [OneSignal.User removeAliases:labels];
}

// OneSignal.User.pushSubscription namespace methods
RCT_EXPORT_METHOD(getOptedIn : (RCTPromiseResolveBlock)resolve
                  reject : (RCTPromiseRejectBlock)reject) {
  resolve(@(OneSignal.User.pushSubscription.optedIn));
}

RCT_EXPORT_METHOD(getPushSubscriptionId : (RCTPromiseResolveBlock)resolve
                  reject : (RCTPromiseRejectBlock)reject) {
  NSString *pushId = OneSignal.User.pushSubscription.id;
  if (pushId && ![pushId isEqualToString:@""]) {
    resolve(pushId);
  } else {
    resolve([NSNull null]);
  }
}

RCT_EXPORT_METHOD(getPushSubscriptionToken : (RCTPromiseResolveBlock)resolve
                  reject : (RCTPromiseRejectBlock)reject) {
  NSString *token = OneSignal.User.pushSubscription.token;
  if (token && ![token isEqualToString:@""]) {
    resolve(token);
  } else {
    resolve([NSNull null]);
  }
}

RCT_EXPORT_METHOD(optIn) { [OneSignal.User.pushSubscription optIn]; }

RCT_EXPORT_METHOD(optOut) { [OneSignal.User.pushSubscription optOut]; }

RCT_EXPORT_METHOD(displayNotification : (NSString *)notificationId) {
  OSNotificationWillDisplayEvent *event =
      _notificationWillDisplayCache[notificationId];
  if (!event) {
    [OneSignalLog
        onesignalLog:ONE_S_LL_ERROR
             message:[NSString
                         stringWithFormat:
                             @"OneSignal (objc): could not find notification "
                             @"will display event for notification with id: %@",
                             notificationId]];
    return;
  }
  dispatch_async(dispatch_get_main_queue(), ^{
    [event.notification display];
  });

  [_preventDefaultCache removeObjectForKey:notificationId];
  [_notificationWillDisplayCache removeObjectForKey:notificationId];
}

- (void)removeObservers {
  [self removePermissionObserver];
  [self removePushSubscriptionObserver];
  [self removeUserStateObserver];
}

- (void)removeHandlers {
  if (_hasAddedInAppMessageClickListener) {
    [OneSignal.InAppMessages removeClickListener:[RCTOneSignal sharedInstance]];
    _hasAddedInAppMessageClickListener = false;
  }
  if (_hasAddedInAppMessageLifecycleListener) {
    [OneSignal.InAppMessages
        removeLifecycleListener:[RCTOneSignal sharedInstance]];
    _hasAddedInAppMessageLifecycleListener = false;
  }
  if (_hasAddedNotificationClickListener) {
    [OneSignal.Notifications removeClickListener:[RCTOneSignal sharedInstance]];
    _hasAddedNotificationClickListener = false;
  }
  if (_hasAddedNotificationForegroundLifecycleListener) {
    [OneSignal.Notifications removeForegroundLifecycleListener:self];
    _hasAddedNotificationForegroundLifecycleListener = false;
  }
}

- (void)removeUserStateObserver {
  if (_hasSetUserStateObserver) {
    [OneSignal.User removeObserver:[RCTOneSignal sharedInstance]];
    _hasSetUserStateObserver = false;
  }
}

RCT_EXPORT_METHOD(trackEvent : (NSString *)name
                  properties : (NSDictionary *_Nullable)properties) {
  NSDictionary *decoded = properties == nil ? nil : OSDecodeNullSentinels(properties);
  [OneSignal.User trackEventWithName:name properties:decoded];
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
    (const facebook::react::ObjCTurboModule::InitParams &)params {
  return std::make_shared<facebook::react::NativeOneSignalSpecJSI>(params);
}

@end

#pragma GCC diagnostic pop
