#import "RNZimReactnativeSdk.h"
#import <React/RCTConvert.h>
#import <ZIM/ZIM.h>
#import "internal/NSDictionary+safeInvoke.h"
#import "internal/NSMutableArray+safeInvoke.h"
#import "internal/NSMutableDictionary+safeInvoke.h"
#import "internal/NSObject+safeInvoke.h"

static NSString *PREFIX = @"im.zego.zim.reactnative.";

#define RN_EVENT(oc_method_name) [NSString stringWithFormat:@"%@%@", PREFIX, oc_method_name]

@interface RCTZIMNativeModule () <ZIMEventHandler>

@property(nonatomic, assign) BOOL hasListeners;
@property(nonatomic, strong) dispatch_queue_t zimQueue;
@property(nonatomic, strong) NSMutableDictionary<NSString *, ZIM *> *engineMap;
@property(nonatomic, strong) NSMapTable<ZIM *, NSString *> *engineMapForCallback;

@end

@implementation RCTZIMNativeModule

RCT_EXPORT_MODULE(ZIMNativeModule)

- (dispatch_queue_t)methodQueue {
  // return dispatch_get_main_queue();
  self.zimQueue = dispatch_queue_create("zego_zim_react_native", DISPATCH_QUEUE_SERIAL);
  [ZIM setCustomDispatchQueue:self.zimQueue];
  return self.zimQueue;
}

+ (BOOL)requiresMainQueueSetup {
  return YES;  // only do this if your module initialization relies on calling UIKit!
}

- (NSDictionary *)constantsToExport {
  return @{@"prefix" : PREFIX};
}

- (void)startObserving {
  // Set up any upstream listeners or background tasks as necessary
  self.hasListeners = YES;
}

// Will be called when this module's last listener is removed, or on dealloc.
- (void)stopObserving {
  // Remove upstream listeners, stop unnecessary background tasks
  self.hasListeners = NO;
}

- (ZIM *)getZIMEngine:(NSString *)handle reject:(RCTPromiseRejectBlock)reject {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return nil;
  }
  return zim;
}

- (BOOL)isSucc:(ZIMError *)errorInfo reject:(RCTPromiseRejectBlock)reject {
  if (errorInfo.code != 0) {
    reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
    return NO;
  }
  return YES;
}

- (NSArray<NSString *> *)supportedEvents {
  return @[
    RN_EVENT(@"error"),
    RN_EVENT(@"connectionStateChanged"),
    RN_EVENT(@"experimentalEventReceived"),
    RN_EVENT(@"tokenWillExpire"),
    RN_EVENT(@"userInfoUpdated"),
    RN_EVENT(@"userRuleUpdated"),
    RN_EVENT(@"userStatusUpdated"),
    RN_EVENT(@"conversationsAllDeleted"),
    RN_EVENT(@"conversationChanged"),
    RN_EVENT(@"conversationSyncStateChanged"),
    RN_EVENT(@"conversationTotalUnreadMessageCountUpdated"),
    RN_EVENT(@"conversationMessageReceiptChanged"),
    RN_EVENT(@"messageReceiptChanged"),
    RN_EVENT(@"messageRevokeReceived"),
    RN_EVENT(@"messageReactionsChanged"),
    RN_EVENT(@"messageRepliedCountChanged"),
    RN_EVENT(@"messageRepliedInfoChanged"),
    RN_EVENT(@"messagePinStatusChanged"),
    RN_EVENT(@"messageEdited"),
    RN_EVENT(@"messageDeleted"),
    RN_EVENT(@"messageSentStatusChanged"),
    RN_EVENT(@"messageReceived"),
    RN_EVENT(@"peerMessageReceived"),
    RN_EVENT(@"roomMessageReceived"),
    RN_EVENT(@"groupMessageReceived"),
    RN_EVENT(@"broadcastMessageReceived"),
    RN_EVENT(@"messageAttachedHook"),
    RN_EVENT(@"messageLoadingProgress"),
    RN_EVENT(@"roomStateChanged"),
    RN_EVENT(@"roomMemberJoined"),
    RN_EVENT(@"roomMemberLeft"),
    RN_EVENT(@"roomAttributesUpdated"),
    RN_EVENT(@"roomAttributesBatchUpdated"),
    RN_EVENT(@"roomMemberAttributesUpdated"),
    RN_EVENT(@"groupStateChanged"),
    RN_EVENT(@"groupNameUpdated"),
    RN_EVENT(@"groupAvatarUrlUpdated"),
    RN_EVENT(@"groupNoticeUpdated"),
    RN_EVENT(@"groupAttributesUpdated"),
    RN_EVENT(@"groupMutedInfoUpdated"),
    RN_EVENT(@"groupVerifyInfoUpdated"),
    RN_EVENT(@"groupApplicationListChanged"),
    RN_EVENT(@"groupApplicationUpdated"),
    RN_EVENT(@"groupMemberStateChanged"),
    RN_EVENT(@"groupMemberInfoUpdated"),
    RN_EVENT(@"groupAliasUpdated"),
    RN_EVENT(@"communityListChanged"),
    RN_EVENT(@"communityInfoUpdated"),
    RN_EVENT(@"communityMemberStateChanged"),
    RN_EVENT(@"communityMemberInfoUpdated"),
    RN_EVENT(@"communityChannelListChanged"),
    RN_EVENT(@"communityChannelInfoUpdated"),
    RN_EVENT(@"callInvitationCreated"),
    RN_EVENT(@"callInvitationReceived"),
    RN_EVENT(@"callInvitationCancelled"),
    RN_EVENT(@"callInvitationTimeout"),
    RN_EVENT(@"callInvitationEnded"),
    RN_EVENT(@"callUserStateChanged"),
    RN_EVENT(@"blacklistChanged"),
    RN_EVENT(@"friendListChanged"),
    RN_EVENT(@"friendInfoUpdated"),
    RN_EVENT(@"friendApplicationListChanged"),
    RN_EVENT(@"friendApplicationUpdated")
  ];
}

RCT_EXPORT_METHOD(writeCustomLog : (NSString *)customLog moduleName : (NSString *)moduleName) {
  SEL mySelector = NSSelectorFromString(@"writeCustomLog:moduleName:");
  if ([ZIM respondsToSelector:mySelector]) {
    [ZIM performSelector:mySelector withObject:customLog withObject:moduleName];
  }
}

RCT_EXPORT_METHOD(getVersion
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  resolve([ZIM getVersion]);
}

RCT_EXPORT_METHOD(setLogConfig : (NSDictionary *)config) {
  ZIMLogConfig *configObj = [[ZIMLogConfig alloc] init];
  if ([config objectForKey:@"logPath"]) {
    configObj.logPath = [RCTConvert NSString:config[@"logPath"]];
  }

  if ([config objectForKey:@"logSize"]) {
    configObj.logSize = [RCTConvert uint64_t:config[@"logSize"]];
  }

  [ZIM setLogConfig:configObj];
}

RCT_EXPORT_METHOD(setCacheConfig : (NSDictionary *)config) {
  ZIMCacheConfig *configObj = [[ZIMCacheConfig alloc] init];
  if ([config objectForKey:@"cachePath"]) {
    configObj.cachePath = [RCTConvert NSString:config[@"cachePath"]];
  }

  [ZIM setCacheConfig:configObj];
}

RCT_EXPORT_METHOD(setAdvancedConfig : (NSString *)key value : (NSString *)value) {
  [ZIM setAdvancedConfigWithKey:key value:value];
}

RCT_EXPORT_METHOD(setGeofencingConfig
                  : (NSArray<NSNumber *> *)areaList type
                  : (int)type resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  resolve([NSNumber numberWithBool:[ZIM setGeofencingConfigWithAreaList:areaList
                                                                   type:(ZIMGeofencingType)type]]);
}

RCT_EXPORT_METHOD(createEngine
                  : (NSString *)handle appID
                  : (NSUInteger)appID appSign
                  : (NSString *)appSign) {
  ZIM *oldZIM = [ZIM getInstance];
  if (oldZIM) {
    [oldZIM destroy];
  }

  ZIMAppConfig *appConfig = [[ZIMAppConfig alloc] init];
  appConfig.appID = (unsigned int)appID;
  appConfig.appSign = appSign;

  ZIM *zim = [ZIM createWithAppConfig:appConfig];
  if (zim) {
    if (!self.engineMap) {
      self.engineMap = [NSMutableDictionary dictionary];
    }

    if (!self.engineMapForCallback) {
      self.engineMapForCallback = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory
                                                        valueOptions:NSMapTableStrongMemory];
    }

    [self.engineMap setObject:zim forKey:handle];
    [self.engineMapForCallback setObject:handle forKey:zim];

    [zim setEventHandler:self];
  }
}

RCT_EXPORT_METHOD(destroyEngine : (NSString *)handle) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (zim) {
    [zim destroy];

    [self.engineMap removeObjectForKey:handle];
    [self.engineMapForCallback removeObjectForKey:zim];
  }
}

RCT_EXPORT_METHOD(uploadLog
                  : (NSString *)handle resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim uploadLog:^(ZIMError *errorInfo) {
    if ([self isSucc:errorInfo reject:reject]) {
      resolve(nil);
    }
  }];
}

RCT_EXPORT_METHOD(callExperimentalAPI
                  : (NSString *)handle params
                  : (NSString *)params resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim callExperimentalAPIWithParams:params
                            callback:^(NSString *result, ZIMError *errorInfo) {
                              if ([self isSucc:errorInfo reject:reject]) {
                                resolve(result);
                              }
                            }];
}

RCT_EXPORT_METHOD(login
                  : (NSString *)handle userID
                  : (NSString *)userID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMLoginConfig *configObj = [[ZIMLoginConfig alloc] init];
  configObj.userName = [RCTConvert NSString:config[@"userName"]];
  configObj.customStatus = [RCTConvert NSString:config[@"customStatus"]];
  configObj.customStatusDuration = [RCTConvert int:config[@"customStatusDuration"]];
  configObj.token = [RCTConvert NSString:config[@"token"]];
  configObj.isOfflineLogin = [RCTConvert BOOL:config[@"isOfflineLogin"]];

  [zim loginWithUserID:userID
                config:configObj
              callback:^(ZIMError *errorInfo) {
                if ([self isSucc:errorInfo reject:reject]) {
                  resolve(nil);
                }
              }];
}

RCT_EXPORT_METHOD(logout
                  : (NSString *)handle resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim logout];

  resolve(nil);
}

RCT_EXPORT_METHOD(renewToken
                  : (NSString *)handle token
                  : (NSString *)token resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim renewToken:token
         callback:^(NSString *token, ZIMError *errorInfo) {
           if ([self isSucc:errorInfo reject:reject]) {
             resolve(@{@"token" : token});
           }
         }];
}

RCT_EXPORT_METHOD(updateUserBadge
                  : (NSString *)handle badge
                  : (int)badge resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateUserBadge:badge
              callback:^(ZIMError *errorInfo) {
                if ([self isSucc:errorInfo reject:reject]) {
                  resolve(nil);
                }
              }];
}

RCT_EXPORT_METHOD(updateUserName
                  : (NSString *)handle userName
                  : (NSString *)userName resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateUserName:userName
             callback:^(NSString *retUserName, ZIMError *errorInfo) {
               if ([self isSucc:errorInfo reject:reject]) {
                 resolve(@{@"userName" : retUserName});
               }
             }];
}

RCT_EXPORT_METHOD(updateUserAvatarUrl
                  : (NSString *)handle userAvatarUrl
                  : (NSString *)userAvatarUrl resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateUserAvatarUrl:userAvatarUrl
                  callback:^(NSString *userAvatarUrl, ZIMError *errorInfo) {
                    if ([self isSucc:errorInfo reject:reject]) {
                      resolve(@{@"userAvatarUrl" : userAvatarUrl});
                    }
                  }];
}

RCT_EXPORT_METHOD(updateUserExtendedData
                  : (NSString *)handle extendedData
                  : (NSString *)extendedData resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateUserExtendedData:extendedData
                     callback:^(NSString *retExtendedData, ZIMError *errorInfo) {
                       if ([self isSucc:errorInfo reject:reject]) {
                         resolve(@{@"extendedData" : retExtendedData});
                       }
                     }];
}

RCT_EXPORT_METHOD(updateUserCustomStatus
                  : (NSString *)handle customStatus
                  : (NSString *)customStatus config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMUserCustomStatusUpdateConfig *configObj = [[ZIMUserCustomStatusUpdateConfig alloc] init];
  if (config && ![config isEqual:[NSNull null]]) {
    configObj.duration = [RCTConvert int:config[@"duration"]];
  }

  [zim updateUserCustomStatus:customStatus
                       config:configObj
                     callback:^(NSString *retcustomStatus, ZIMError *errorInfo) {
                       if ([self isSucc:errorInfo reject:reject]) {
                         resolve(@{@"customStatus" : retcustomStatus});
                       }
                     }];
}

RCT_EXPORT_METHOD(updateUserOfflinePushRule
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMUserOfflinePushRule *configObj = [[ZIMUserOfflinePushRule alloc] init];

  NSMutableArray *onlineArray = config[@"onlinePlatforms"];
  if (onlineArray != nil) {
    configObj.onlinePlatforms = onlineArray;
  }
  NSMutableArray *offlineArray = config[@"notToReceiveOfflinePushPlatforms"];
  if (offlineArray != nil) {
    configObj.notToReceiveOfflinePushPlatforms = offlineArray;
  }

  [zim updateUserOfflinePushRule:configObj
                        callback:^(ZIMUserOfflinePushRule *info, ZIMError *errorInfo) {
                          if ([self isSucc:errorInfo reject:reject]) {
                            NSMutableDictionary *infoMap = [NSMutableDictionary dictionary];
                            infoMap[@"onlinePlatforms"] = info.onlinePlatforms;
                            infoMap[@"notToReceiveOfflinePushPlatforms"] =
                                info.notToReceiveOfflinePushPlatforms;
                            resolve(@{@"offlinePushRule" : infoMap});
                          }
                        }];
}

RCT_EXPORT_METHOD(queryUsersInfo
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMUsersInfoQueryConfig *configObj = [[ZIMUsersInfoQueryConfig alloc] init];
  configObj.isQueryFromServer = [RCTConvert BOOL:config[@"isQueryFromServer"]];

  [zim queryUsersInfo:userIDs
               config:configObj
             callback:^(NSArray<ZIMUserFullInfo *> *userList,
                        NSArray<ZIMErrorUserInfo *> *errorUserList, ZIMError *errorInfo) {
               if ([self isSucc:errorInfo reject:reject]) {
                 NSMutableArray<NSDictionary *> *userInfoArray = [NSMutableArray array];
                 for (ZIMUserFullInfo *userInfo in userList) {
                   [userInfoArray addObject:[self mZIMUserFullInfo:userInfo]];
                 }

                 resolve(@{
                   @"userList" : userInfoArray,
                   @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                 });
               }
             }];
}

RCT_EXPORT_METHOD(subscribeUsersStatus
                  : (NSString *)handle isSub
                  : (BOOL)isSub userIDs
                  : (NSArray<NSString *> *)userIDs duration
                  : (int)duration resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  if (isSub) {
    ZIMUserStatusSubscribeConfig *config = [[ZIMUserStatusSubscribeConfig alloc] init];
    config.subscriptionDuration = duration;

    [zim subscribeUsersStatus:userIDs
                       config:config
                     callback:^(NSArray<ZIMErrorUserInfo *> *errorUserList, ZIMError *errorInfo) {
                       if ([self isSucc:errorInfo reject:reject]) {
                         resolve(@{@"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]});
                       }
                     }];

  } else {
    [zim
        unsubscribeUsersStatus:userIDs
                      callback:^(NSArray<ZIMErrorUserInfo *> *errorUserList, ZIMError *errorInfo) {
                        if ([self isSucc:errorInfo reject:reject]) {
                          resolve(@{@"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]});
                        }
                      }];
  }
}

RCT_EXPORT_METHOD(queryUsersStatus
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }
  [zim
      queryUsersStatusByUserIDs:userIDs
                       callback:^(NSArray<ZIMUserStatus *> *userStatusList,
                                  NSArray<ZIMErrorUserInfo *> *errorUserList, ZIMError *errorInfo) {
                         if ([self isSucc:errorInfo reject:reject]) {
                           resolve(@{
                             @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList],
                             @"userStatusList" : [self mZIMUserStatusList:userStatusList]
                           });
                         }
                       }];
}

RCT_EXPORT_METHOD(querySubscribedUsersStatus
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMSubscribedUserStatusQueryConfig *config = [[ZIMSubscribedUserStatusQueryConfig alloc] init];
  config.userIDs = userIDs;

  [zim querySubscribedUserStatusListWithConfig:config
                                      callback:^(NSArray<ZIMUserStatusSubscription *>
                                                     *userStatusSubscriptionList,
                                                 ZIMError *errorInfo) {
                                        if ([self isSucc:errorInfo reject:reject]) {
                                          resolve(@{
                                            @"userStatusSubscriptionList" :
                                                [self mZIMUserStatusSubsciptionList:
                                                          userStatusSubscriptionList]
                                          });
                                        }
                                      }];
}

RCT_EXPORT_METHOD(querySelfUserInfo
                  : (NSString *)handle resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim querySelfUserInfo:^(ZIMSelfUserInfo *info, ZIMError *errorInfo) {
    if ([self isSucc:errorInfo reject:reject]) {
      NSMutableDictionary *infoMap = [NSMutableDictionary dictionary];
      infoMap[@"userRule"] = [self mZIMUserRule:info.userRule];
      infoMap[@"userFullInfo"] = [self mZIMUserFullInfo:info.userFullInfo];
      infoMap[@"userStatus"] = [self mZIMUserStatus:info.userStatus];
      resolve(@{@"selfUserInfo" : infoMap});
    }
  }];
}

RCT_EXPORT_METHOD(queryLocalFileCache
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMFileCacheQueryConfig *configObj = [[ZIMFileCacheQueryConfig alloc] init];
  configObj.endTime = [RCTConvert uint64_t:config[@"endTime"]];

  [zim queryLocalFileCacheWithConfig:configObj
                            callback:^(ZIMFileCacheInfo *info, ZIMError *errorInfo) {
                              if ([self isSucc:errorInfo reject:reject]) {
                                resolve(@{@"totalFileSize" : @(info.totalFileSize)});
                              }
                            }];
}

RCT_EXPORT_METHOD(clearLocalFileCache
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMFileCacheClearConfig *configObj = [[ZIMFileCacheClearConfig alloc] init];
  configObj.endTime = [RCTConvert uint64_t:config[@"endTime"]];

  [zim clearLocalFileCacheWithConfig:configObj
                            callback:^(ZIMError *errorInfo) {
                              if ([self isSucc:errorInfo reject:reject]) {
                                resolve(nil);
                              }
                            }];
}

RCT_EXPORT_METHOD(queryConversation
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryConversation:conversationID
        conversationType:(ZIMConversationType)conversationType
                callback:^(ZIMConversation *conversation, ZIMError *errorInfo) {
                  if ([self isSucc:errorInfo reject:reject]) {
                    resolve(@{@"conversation" : [self mZIMConversation:conversation]});
                  }
                }];
}

RCT_EXPORT_METHOD(queryConversationList
                  : (NSString *)handle config
                  : (NSDictionary *)config option
                  : (NSDictionary *)option resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMConversationQueryConfig *configObj = [[ZIMConversationQueryConfig alloc] init];
  configObj.count = [RCTConvert int:config[@"count"]];
  configObj.nextConversation = [self oZIMConversation:config[@"nextConversation"]];

  ZIMConversationFilterOption *optionObj = [[ZIMConversationFilterOption alloc] init];
  optionObj.isOnlyUnreadConversation = [RCTConvert BOOL:option[@"isOnlyUnreadConversation"]];
  optionObj.pinnedFilter = (ZIMConversationPinnedFilter)[RCTConvert int:option[@"pinnedFilter"]];
  NSMutableArray *markArray = option[@"marks"];
  if (markArray != nil) {
    optionObj.marks = markArray;
  }
  NSMutableArray *typeArray = option[@"conversationTypes"];
  if (typeArray != nil) {
    optionObj.conversationTypes = typeArray;
  }

  [zim queryConversationListWithConfig:configObj
                                option:optionObj
                              callback:^(NSArray<ZIMConversation *> *conversationList,
                                         ZIMError *errorInfo) {
                                if ([self isSucc:errorInfo reject:reject]) {
                                  resolve(@{
                                    @"conversationList" :
                                        [self mZIMConversationList:conversationList]
                                  });
                                }
                              }];
}

RCT_EXPORT_METHOD(queryConversationPinnedList
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMConversationQueryConfig *configObj = [[ZIMConversationQueryConfig alloc] init];
  configObj.count = [RCTConvert int:config[@"count"]];
  configObj.nextConversation = [self oZIMConversation:config[@"nextConversation"]];

  [zim queryConversationPinnedListWithConfig:configObj
                                    callback:^(NSArray<ZIMConversation *> *conversationList,
                                               ZIMError *errorInfo) {
                                      if ([self isSucc:errorInfo reject:reject]) {
                                        resolve(@{
                                          @"conversationList" :
                                              [self mZIMConversationList:conversationList]
                                        });
                                      }
                                    }];
}

RCT_EXPORT_METHOD(queryConversationTotalUnreadMessageCount
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMConversationTotalUnreadMessageCountQueryConfig *configObj =
      [[ZIMConversationTotalUnreadMessageCountQueryConfig alloc] init];
  NSMutableArray *markArray = config[@"marks"];
  if (markArray != nil) {
    configObj.marks = markArray;
  }
  NSMutableArray *typeArray = config[@"conversationTypes"];
  if (typeArray != nil) {
    configObj.conversationTypes = typeArray;
  }

  [zim queryConversationTotalUnreadMessageCountWithConfig:configObj
                                                 callback:^(unsigned int unreadMessageCount,
                                                            ZIMError *errorInfo) {
                                                   if ([self isSucc:errorInfo reject:reject]) {
                                                     resolve(@{
                                                       @"unreadMessageCount" : @(unreadMessageCount)
                                                     });
                                                   }
                                                 }];
}

RCT_EXPORT_METHOD(deleteConversation
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMConversationDeleteConfig *configObj = [[ZIMConversationDeleteConfig alloc] init];
  configObj.isAlsoDeleteServerConversation =
      [RCTConvert BOOL:config[@"isAlsoDeleteServerConversation"]];

  [zim deleteConversation:conversationID
         conversationType:(ZIMConversationType)conversationType
                   config:configObj
                 callback:^(NSString *conversationID, ZIMConversationType conversationType,
                            ZIMError *errorInfo) {
                   if ([self isSucc:errorInfo reject:reject]) {
                     resolve(@{
                       @"conversationID" : conversationID,
                       @"conversationType" : @(conversationType)
                     });
                   }
                 }];
}

RCT_EXPORT_METHOD(deleteAllConversations
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMConversationDeleteConfig *configObj = [[ZIMConversationDeleteConfig alloc] init];
  configObj.isAlsoDeleteServerConversation =
      [RCTConvert BOOL:config[@"isAlsoDeleteServerConversation"]];

  [zim deleteAllConversationsWithConfig:configObj
                               callback:^(ZIMError *errorInfo) {
                                 if ([self isSucc:errorInfo reject:reject]) {
                                   resolve(nil);
                                 }
                               }];
}

RCT_EXPORT_METHOD(setConversationNotificationStatus
                  : (NSString *)handle status
                  : (int)status conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim setConversationNotificationStatus:(ZIMConversationNotificationStatus)status
                          conversationID:conversationID
                        conversationType:(ZIMConversationType)conversationType
                                callback:^(NSString *conversationID,
                                           ZIMConversationType conversationType,
                                           ZIMError *errorInfo) {
                                  if ([self isSucc:errorInfo reject:reject]) {
                                    resolve(@{
                                      @"conversationID" : conversationID,
                                      @"conversationType" : @(conversationType)
                                    });
                                  }
                                }];
}

RCT_EXPORT_METHOD(setConversationDraft
                  : (NSString *)handle draft
                  : (NSString *)draft conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim setConversationDraft:draft
             conversationID:conversationID
           conversationType:(ZIMConversationType)conversationType
                   callback:^(NSString *conversationID, ZIMConversationType conversationType,
                              ZIMError *errorInfo) {
                     if ([self isSucc:errorInfo reject:reject]) {
                       resolve(@{
                         @"conversationID" : conversationID,
                         @"conversationType" : @(conversationType)
                       });
                     }
                   }];
}

RCT_EXPORT_METHOD(updateConversationPinnedState
                  : (NSString *)handle isPinned
                  : (BOOL)isPinned conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateConversationPinnedState:isPinned
                      conversationID:conversationID
                    conversationType:(ZIMConversationType)conversationType
                            callback:^(NSString *conversationID,
                                       ZIMConversationType conversationType, ZIMError *errorInfo) {
                              if ([self isSucc:errorInfo reject:reject]) {
                                resolve(@{
                                  @"conversationID" : conversationID,
                                  @"conversationType" : @(conversationType)
                                });
                              }
                            }];
}

RCT_EXPORT_METHOD(setConversationMark
                  : (NSString *)handle markType
                  : (int)markType enable
                  : (BOOL)enable conversationInfos
                  : (NSArray<NSDictionary *> *)conversationInfos resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  NSMutableArray<ZIMConversationBaseInfo *> *infoArray = [NSMutableArray array];
  for (NSDictionary *dict in conversationInfos) {
    ZIMConversationBaseInfo *info = [[ZIMConversationBaseInfo alloc] init];
    info.conversationID = [RCTConvert NSString:dict[@"conversationID"]];
    info.conversationType = (ZIMConversationType)[RCTConvert int:dict[@"conversationType"]];
    [infoArray addObject:info];
  }

  [zim setConversationMark:[NSNumber numberWithInt:markType]
                    enable:enable
         conversationInfos:infoArray
                  callback:^(NSArray<ZIMConversationBaseInfo *> *failedConversationInfos,
                             ZIMError *errorInfo) {
                    if ([self isSucc:errorInfo reject:reject]) {
                      NSMutableArray<NSDictionary *> *failedArray = [NSMutableArray array];
                      for (ZIMConversationBaseInfo *info in failedConversationInfos) {
                        [failedArray addObject:@{
                          @"conversationID" : info.conversationID,
                          @"conversationType" : @(info.conversationType)
                        }];
                      }
                      resolve(@{@"failedConversationInfos" : failedArray});
                    }
                  }];
}

RCT_EXPORT_METHOD(setConversationDestructDuration
                  : (NSString *)handle duration
                  : (int *)duration conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim setConversationMessageDestructDuration:duration
                               conversationID:conversationID
                             conversationType:(ZIMConversationType)conversationType
                                     callback:^(NSString *conversationID,
                                                ZIMConversationType conversationType,
                                                ZIMError *errorInfo) {
                                       if ([self isSucc:errorInfo reject:reject]) {
                                         resolve(@{
                                           @"conversationID" : conversationID,
                                           @"conversationType" : @(conversationType)
                                         });
                                       }
                                     }];
}

RCT_EXPORT_METHOD(clearConversationUnreadMessageCount
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim clearConversationUnreadMessageCount:conversationID
                          conversationType:conversationType
                                  callback:^(NSString *conversationID,
                                             ZIMConversationType conversationType,
                                             ZIMError *errorInfo) {
                                    if ([self isSucc:errorInfo reject:reject]) {
                                      resolve(@{
                                        @"conversationID" : conversationID,
                                        @"conversationType" : @(conversationType)
                                      });
                                    }
                                  }];
}

RCT_EXPORT_METHOD(clearConversationTotalUnreadMessageCount
                  : (NSString *)handle resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim clearConversationTotalUnreadMessageCount:^(ZIMError *errorInfo) {
    if ([self isSucc:errorInfo reject:reject]) {
      resolve(nil);
    }
  }];
}

RCT_EXPORT_METHOD(deleteAllConversationMessages
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessageDeleteConfig *configObj = [[ZIMMessageDeleteConfig alloc] init];
  configObj.isAlsoDeleteServerMessage = [RCTConvert BOOL:config[@"isAlsoDeleteServerMessage"]];

  [zim deleteAllConversationMessagesWithConfig:configObj
                                      callback:^(ZIMError *errorInfo) {
                                        if ([self isSucc:errorInfo reject:reject]) {
                                          resolve(nil);
                                        }
                                      }];
}

RCT_EXPORT_METHOD(sendMessage
                  : (NSString *)handle message
                  : (NSDictionary *)message conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType config
                  : (NSDictionary *)config _handle
                  : (NSString *)_handle resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:message];

  ZIMMessageSendConfig *configObj = [[ZIMMessageSendConfig alloc] init];
  configObj.priority = (ZIMMessagePriority)[RCTConvert int:config[@"priority"]];
  configObj.hasReceipt = [RCTConvert BOOL:config[@"hasReceipt"]];
  configObj.isRetrySend = [RCTConvert BOOL:config[@"isRetrySend"]];
  configObj.isNotifyMentionedUsers = [RCTConvert BOOL:config[@"isNotifyMentionedUsers"]];
  configObj.disableUnreadMessageCount = [RCTConvert BOOL:config[@"disableUnreadMessageCount"]];
  configObj.isAllowMultipleMessagePartialFailed =
      [RCTConvert BOOL:config[@"isAllowMultipleMessagePartialFailed"]];
  configObj.pushConfig = [self mZIMPushConfig:config];

  ZIMMessageSendNotification *notification = [[ZIMMessageSendNotification alloc] init];

  notification.onMessageAttached = ^(ZIMMessage *message) {
    NSDictionary *messageDic = [self mZIMMessage:message];
    [self sendEventWithName:RN_EVENT(@"messageAttachedHook")
                       body:@{
                         @"handle" : _handle,
                         @"data" : @[ messageDic ]
                       }];
  };

  notification.onMediaUploadingProgress =
      ^(ZIMMediaMessage *message, unsigned long long currentFileSize,
        unsigned long long totalFileSize) {
        NSDictionary *messageDic = [self mZIMMessage:message];
        [self sendEventWithName:RN_EVENT(@"messageLoadingProgress")
                           body:@{
                             @"handle" : _handle,
                             @"data" : @[ messageDic, @(currentFileSize), @(totalFileSize) ]
                           }];
      };

  notification.onMultipleMediaUploadingProgress =
      ^(ZIMMultipleMessage *message, unsigned long long currentFileSize,
        unsigned long long totalFileSize, unsigned int messageInfoIndex,
        unsigned long long currentIndexFileSize, unsigned long long totalIndexFileSize) {
        NSDictionary *messageDic = [self mZIMMessage:message];
        [self sendEventWithName:RN_EVENT(@"messageLoadingProgress")
                           body:@{
                             @"handle" : _handle,
                             @"data" : @[
                               messageDic, @(currentFileSize), @(totalFileSize),
                               @(messageInfoIndex), @(currentIndexFileSize), @(totalIndexFileSize)
                             ]
                           }];
      };

  [zim sendMessage:messageObj
      toConversationID:conversationID
      conversationType:(ZIMConversationType)conversationType
                config:configObj
          notification:notification
              callback:^(ZIMMessage *message, ZIMError *errorInfo) {
                if ([self isSucc:errorInfo reject:reject]) {
                  NSDictionary *messageDict = [self mZIMMessage:message];
                  resolve(@{@"message" : messageDict});
                }
              }];
}

RCT_EXPORT_METHOD(editMessage
                  : (NSString *)handle message
                  : (NSDictionary *)message _handle
                  : (NSString *)_handle resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:message];

  ZIMMessageEditConfig *configObj = [[ZIMMessageEditConfig alloc] init];

  ZIMMessageSendNotification *notification = [[ZIMMessageSendNotification alloc] init];

  notification.onMessageAttached = ^(ZIMMessage *message) {
    NSDictionary *messageDic = [self mZIMMessage:message];
    [self sendEventWithName:RN_EVENT(@"messageAttachedHook")
                       body:@{
                         @"handle" : _handle,
                         @"data" : @[ messageDic ]
                       }];
  };

  notification.onMultipleMediaUploadingProgress =
      ^(ZIMMultipleMessage *message, unsigned long long currentFileSize,
        unsigned long long totalFileSize, unsigned int messageInfoIndex,
        unsigned long long currentIndexFileSize, unsigned long long totalIndexFileSize) {
        NSDictionary *messageDic = [self mZIMMessage:message];
        [self sendEventWithName:RN_EVENT(@"messageLoadingProgress")
                           body:@{
                             @"handle" : _handle,
                             @"data" : @[
                               messageDic, @(currentFileSize), @(totalFileSize),
                               @(messageInfoIndex), @(currentIndexFileSize), @(totalIndexFileSize)
                             ]
                           }];
      };

  [zim editMessage:messageObj
            config:configObj
      notification:notification
          callback:^(ZIMMessage *message, ZIMError *errorInfo) {
            if ([self isSucc:errorInfo reject:reject]) {
              NSDictionary *messageDict = [self mZIMMessage:message];
              resolve(@{@"message" : messageDict});
            }
          }];
}

RCT_EXPORT_METHOD(deleteMessages
                  : (NSString *)handle messageList
                  : (NSArray<NSDictionary *> *)messageList conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessageDeleteConfig *configObj = [[ZIMMessageDeleteConfig alloc] init];
  configObj.isAlsoDeleteServerMessage = [RCTConvert BOOL:config[@"isAlsoDeleteServerMessage"]];

  [zim deleteMessages:[self oZIMMessageList:messageList]
        conversationID:conversationID
      conversationType:(ZIMConversationType)conversationType
                config:configObj
              callback:^(NSString *conversationID, ZIMConversationType conversationType,
                         ZIMError *errorInfo) {
                if ([self isSucc:errorInfo reject:reject]) {
                  resolve(@{
                    @"conversationID" : conversationID,
                    @"conversationType" : @(conversationType)
                  });
                }
              }];
}

RCT_EXPORT_METHOD(deleteAllMessage
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessageDeleteConfig *configObj = [[ZIMMessageDeleteConfig alloc] init];
  configObj.isAlsoDeleteServerMessage = [RCTConvert BOOL:config[@"isAlsoDeleteServerMessage"]];

  [zim deleteAllMessageByConversationID:conversationID
                       conversationType:(ZIMConversationType)conversationType
                                 config:configObj
                               callback:^(NSString *conversationID,
                                          ZIMConversationType conversationType,
                                          ZIMError *errorInfo) {
                                 if ([self isSucc:errorInfo reject:reject]) {
                                   resolve(@{
                                     @"conversationID" : conversationID,
                                     @"conversationType" : @(conversationType)
                                   });
                                 }
                               }];
}

RCT_EXPORT_METHOD(queryHistoryMessage
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessageQueryConfig *configObj = [[ZIMMessageQueryConfig alloc] init];
  configObj.count = (unsigned int)[RCTConvert uint64_t:config[@"count"]];
  configObj.reverse = [RCTConvert BOOL:config[@"reverse"]];
  configObj.mode = (ZIMMessageQueryMode)[RCTConvert uint64_t:config[@"mode"]];
  configObj.nextMessage = [self oZIMMessage:config[@"nextMessage"]];

  [zim
      queryHistoryMessageByConversationID:conversationID
                         conversationType:(ZIMConversationType)conversationType
                                   config:configObj
                                 callback:^(
                                     NSString *conversationID, ZIMConversationType conversationType,
                                     NSArray<ZIMMessage *> *messageList, ZIMError *errorInfo) {
                                   if ([self isSucc:errorInfo reject:reject]) {
                                     resolve(@{
                                       @"conversationID" : conversationID,
                                       @"conversationType" : @(conversationType),
                                       @"messageList" : [self mZIMMessageList:messageList]
                                     });
                                   }
                                 }];
}

RCT_EXPORT_METHOD(downloadMediaFile
                  : (NSString *)handle message
                  : (NSDictionary *)messageDict fileType
                  : (int)fileType config
                  : (NSDictionary *)config _handle
                  : (NSString *)_handle resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:messageDict];

  ZIMMediaDownloadConfig *configObj = [[ZIMMediaDownloadConfig alloc] init];
  configObj.messageInfoIndex = (unsigned int)[RCTConvert uint64_t:config[@"messageInfoIndex"]];

  [zim downloadMediaFileWithMessage:messageObj
      fileType:(ZIMMediaFileType)fileType
      config:configObj
      progress:^(ZIMMessage *message, unsigned long long currentFileSize,
                 unsigned long long totalFileSize) {
        NSDictionary *messageDic = [self mZIMMessage:message];
        [self sendEventWithName:RN_EVENT(@"messageLoadingProgress")
                           body:@{
                             @"handle" : _handle,
                             @"data" : @[ messageDic, @(currentFileSize), @(totalFileSize) ]
                           }];
      }
      callback:^(ZIMMessage *message, ZIMError *errorInfo) {
        if ([self isSucc:errorInfo reject:reject]) {
          NSDictionary *messageDic = [self mZIMMessage:message];
          resolve(@{@"message" : messageDic});
        }
      }];
}

RCT_EXPORT_METHOD(insertMessageToLocalDB
                  : (NSString *)handle message
                  : (NSDictionary *)messageDict conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType senderUserID
                  : (NSString *)senderUserID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:messageDict];

  [zim insertMessageToLocalDB:messageObj
               conversationID:conversationID
             conversationType:conversationType
                 senderUserID:senderUserID
                     callback:^(ZIMMessage *message, ZIMError *errorInfo) {
                       if ([self isSucc:errorInfo reject:reject]) {
                         NSDictionary *messageDic = [self mZIMMessage:message];
                         resolve(@{@"message" : messageDic});
                       }
                     }];
}

RCT_EXPORT_METHOD(updateMessageLocalExtendedData
                  : (NSString *)handle localExtendedData
                  : (NSString *)localExtendedData message
                  : (NSDictionary *)messageDict resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:messageDict];

  [zim updateMessageLocalExtendedData:localExtendedData
                              message:messageObj
                             callback:^(ZIMMessage *message, ZIMError *errorInfo) {
                               if ([self isSucc:errorInfo reject:reject]) {
                                 NSDictionary *messageDic = [self mZIMMessage:message];
                                 resolve(@{@"message" : messageDic});
                               }
                             }];
}

RCT_EXPORT_METHOD(cancelSendingMessage
                  : (NSString *)handle message
                  : (NSDictionary *)messageDict config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMSendingMessageCancelConfig *configObj = [[ZIMSendingMessageCancelConfig alloc] init];

  ZIMMessage *messageObj = [self oZIMMessage:messageDict];

  [zim cancelSendingMessage:messageObj
                     config:configObj
                   callback:^(ZIMError *errorInfo) {
                     if ([self isSucc:errorInfo reject:reject]) {
                       resolve(nil);
                     }
                   }];
}

RCT_EXPORT_METHOD(sendConversationMessageReceiptRead
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim sendConversationMessageReceiptRead:conversationID
                         conversationType:conversationType
                                 callback:^(NSString *conversationID,
                                            ZIMConversationType conversationType,
                                            ZIMError *errorInfo) {
                                   if ([self isSucc:errorInfo reject:reject]) {
                                     resolve(@{
                                       @"conversationID" : conversationID,
                                       @"conversationType" : @(conversationType)
                                     });
                                   }
                                 }];
}

RCT_EXPORT_METHOD(sendMessageReceiptsRead
                  : (NSString *)handle messageList
                  : (NSArray<NSDictionary *> *)messageList conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim sendMessageReceiptsRead:[self oZIMMessageList:messageList]
                conversationID:conversationID
              conversationType:conversationType
                      callback:^(NSString *conversationID, ZIMConversationType conversationType,
                                 NSArray<NSNumber *> *errorMessageIDs, ZIMError *errorInfo) {
                        if ([self isSucc:errorInfo reject:reject]) {
                          resolve(@{
                            @"conversationID" : conversationID,
                            @"conversationType" : @(conversationType),
                            @"errorMessageIDs" : [self mNumberToStringList:errorMessageIDs]
                          });
                        }
                      }];
}

RCT_EXPORT_METHOD(queryMessageReceiptsInfo
                  : (NSString *)handle messageList
                  : (NSArray<NSDictionary *> *)messageList conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryMessageReceiptsInfoByMessageList:[self oZIMMessageList:messageList]
                              conversationID:conversationID
                            conversationType:conversationType
                                    callback:^(NSArray<ZIMMessageReceiptInfo *> *infos,
                                               NSArray<NSNumber *> *errorMessageIDs,
                                               ZIMError *errorInfo) {
                                      if ([self isSucc:errorInfo reject:reject]) {
                                        resolve(@{
                                          @"infos" : [self mZIMMessageReceiptInfoList:infos],
                                          @"errorMessageIDs" :
                                              [self mNumberToStringList:errorMessageIDs]
                                        });
                                      }
                                    }];
}

RCT_EXPORT_METHOD(queryGroupMessageReceiptMemberList
                  : (NSString *)handle message
                  : (NSDictionary *)message groupID
                  : (NSString *)groupID count
                  : (int)count config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:message];

  ZIMGroupMessageReceiptMemberListQueryConfig *configObj =
      [[ZIMGroupMessageReceiptMemberListQueryConfig alloc] init];
  configObj.filterType = (ZIMMessageReceiptFilterType)[RCTConvert uint64_t:config[@"filterType"]];
  configObj.nextFlag = (unsigned int)[RCTConvert uint64_t:config[@"nextFlag"]];

  [zim
      queryGroupMessageReceiptMemberListByMessage:messageObj
                                          groupID:groupID
                                            count:(unsigned int)count
                                           config:configObj
                                         callback:^(NSString *groupID,
                                                    NSArray<ZIMGroupMemberInfo *> *userList,
                                                    unsigned int nextFlag, ZIMError *errorInfo) {
                                           if ([self isSucc:errorInfo reject:reject]) {
                                             resolve(@{
                                               @"groupID" : groupID,
                                               @"nextFlag" : @(nextFlag),
                                               @"userList" : [self mZIMGroupMemberInfoList:userList]
                                             });
                                           }
                                         }];
}

RCT_EXPORT_METHOD(revokeMessage
                  : (NSString *)handle message
                  : (NSDictionary *)message config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:message];

  ZIMMessageRevokeConfig *configObj = [[ZIMMessageRevokeConfig alloc] init];
  configObj.revokeExtendedData = [RCTConvert NSString:config[@"revokeExtendedData"]];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim revokeMessage:messageObj
              config:configObj
            callback:^(ZIMMessage *message, ZIMError *errorInfo) {
              if ([self isSucc:errorInfo reject:reject]) {
                resolve(@{@"message" : [self mZIMMessage:message]});
              }
            }];
}

RCT_EXPORT_METHOD(queryCombineMessageDetail
                  : (NSString *)handle message
                  : (NSDictionary *)message resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:message];

  [zim queryCombineMessageDetailByMessage:(ZIMCombineMessage *)messageObj
                                 callback:^(ZIMCombineMessage *message, ZIMError *errorInfo) {
                                   if ([self isSucc:errorInfo reject:reject]) {
                                     resolve(@{@"message" : [self mZIMMessage:message]});
                                   }
                                 }];
}

RCT_EXPORT_METHOD(pinMessage
                  : (NSString *)handle message
                  : (NSDictionary *)message isPinned
                  : (BOOL)isPinned config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:message];

  ZIMMessagePinConfig *configObj = [[ZIMMessagePinConfig alloc] init];
  configObj.disableAutoReplace = [RCTConvert BOOL:config[@"disableAutoReplace"]];

  [zim pinMessage:messageObj
         isPinned:isPinned
           config:configObj
         callback:^(ZIMError *errorInfo) {
           if ([self isSucc:errorInfo reject:reject]) {
             resolve(nil);
           }
         }];
}

RCT_EXPORT_METHOD(queryPinnedMessageList
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim
      queryPinnedMessageListByConversationID:conversationID
                            conversationType:conversationType
                                    callback:^(NSArray<ZIMMessage *> *messageList,
                                               ZIMError *errorInfo) {
                                      if ([self isSucc:errorInfo reject:reject]) {
                                        resolve(
                                            @{@"messageList" : [self mZIMMessageList:messageList]});
                                      }
                                    }];
}

RCT_EXPORT_METHOD(addMessageReaction
                  : (NSString *)handle reactionType
                  : (NSString *)reactionType message
                  : (NSDictionary *)message config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:message];

  ZIMMessageReactionAddConfig *configObj = [[ZIMMessageReactionAddConfig alloc] init];
  configObj.increaseCount = [RCTConvert int:config[@"increaseCount"]];

  [zim addMessageReaction:reactionType
                  message:messageObj
                   config:configObj
                 callback:^(ZIMMessageReaction *reaction, ZIMError *errorInfo) {
                   if ([self isSucc:errorInfo reject:reject]) {
                     resolve(@{@"reaction" : [self mZIMMessageReaction:reaction]});
                   }
                 }];
}

RCT_EXPORT_METHOD(deleteMessageReaction
                  : (NSString *)handle reactionType
                  : (NSString *)reactionType message
                  : (NSDictionary *)message resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:message];

  [zim deleteMessageReaction:reactionType
                     message:messageObj
                    callback:^(ZIMMessageReaction *reaction, ZIMError *errorInfo) {
                      if ([self isSucc:errorInfo reject:reject]) {
                        resolve(@{@"reaction" : [self mZIMMessageReaction:reaction]});
                      }
                    }];
}

RCT_EXPORT_METHOD(deleteMessageAllReactions
                  : (NSString *)handle message
                  : (NSDictionary *)message resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:message];

  [zim deleteMessageAllReactionsByMessage:messageObj
                                 callback:^(ZIMError *errorInfo) {
                                   if ([self isSucc:errorInfo reject:reject]) {
                                     resolve(nil);
                                   }
                                 }];
}

RCT_EXPORT_METHOD(queryMessageReactionUserList
                  : (NSString *)handle message
                  : (NSDictionary *)message config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:message];

  ZIMMessageReactionUserQueryConfig *configObj = [[ZIMMessageReactionUserQueryConfig alloc] init];
  configObj.reactionType = [RCTConvert NSString:config[@"reactionType"]];
  configObj.count = (unsigned int)[RCTConvert uint64_t:config[@"count"]];
  configObj.nextFlag = (unsigned int)[RCTConvert uint64_t:config[@"nextFlag"]];

  [zim queryMessageReactionUserListByMessage:messageObj
                                      config:configObj
                                    callback:^(
                                        ZIMMessage *message,
                                        NSArray<ZIMMessageReactionUserFullInfo *> *userInfoList,
                                        NSString *reactionType, long long nextFlag,
                                        unsigned int totalCount, ZIMError *errorInfo) {
                                      if ([self isSucc:errorInfo reject:reject]) {
                                        NSMutableArray *infosArray = [NSMutableArray array];
                                        for (ZIMMessageReactionUserFullInfo *user in userInfoList) {
                                          NSMutableArray *reactionsArray = [NSMutableArray array];
                                          for (ZIMMessageReactionSimpleInfo *info in user
                                                   .reactions) {
                                            [reactionsArray addObject:@{
                                              @"reactionType" : info.reactionType,
                                              @"sumCount" : @(info.sumCount)
                                            }];
                                          }

                                          [infosArray addObject:@{
                                            @"userID" : user.userID,
                                            @"reactions" : reactionsArray
                                          }];
                                        }

                                        resolve(@{
                                          @"reactionType" : reactionType,
                                          @"nextFlag" : @(nextFlag),
                                          @"totalCount" : @(totalCount),
                                          @"userInfoList" : infosArray,
                                          @"message" : [self mZIMMessage:message]
                                        });
                                      }
                                    }];
}

RCT_EXPORT_METHOD(replyMessage
                  : (NSString *)handle message
                  : (NSDictionary *)message originMessage
                  : (NSDictionary *)originMessage config
                  : (NSDictionary *)config _handle
                  : (NSString *)_handle resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:message];
  ZIMMessage *originMessageObj = [self oZIMMessage:originMessage];

  ZIMMessageSendConfig *configObj = [[ZIMMessageSendConfig alloc] init];
  configObj.priority = (ZIMMessagePriority)[RCTConvert int:config[@"priority"]];
  configObj.hasReceipt = [RCTConvert BOOL:config[@"hasReceipt"]];
  configObj.isRetrySend = [RCTConvert BOOL:config[@"isRetrySend"]];
  configObj.isNotifyMentionedUsers = [RCTConvert BOOL:config[@"isNotifyMentionedUsers"]];
  configObj.disableUnreadMessageCount = [RCTConvert BOOL:config[@"disableUnreadMessageCount"]];
  configObj.isAllowMultipleMessagePartialFailed =
      [RCTConvert BOOL:config[@"isAllowMultipleMessagePartialFailed"]];
  configObj.pushConfig = [self mZIMPushConfig:config];

  ZIMMessageSendNotification *notification = [[ZIMMessageSendNotification alloc] init];

  notification.onMessageAttached = ^(ZIMMessage *message) {
    NSDictionary *messageDic = [self mZIMMessage:message];
    [self sendEventWithName:RN_EVENT(@"messageAttachedHook")
                       body:@{
                         @"handle" : _handle,
                         @"data" : @[ messageDic ]
                       }];
  };

  notification.onMediaUploadingProgress =
      ^(ZIMMediaMessage *message, unsigned long long currentFileSize,
        unsigned long long totalFileSize) {
        NSDictionary *messageDic = [self mZIMMessage:message];
        [self sendEventWithName:RN_EVENT(@"messageLoadingProgress")
                           body:@{
                             @"handle" : _handle,
                             @"data" : @[ messageDic, @(currentFileSize), @(totalFileSize) ]
                           }];
      };

  notification.onMultipleMediaUploadingProgress =
      ^(ZIMMultipleMessage *message, unsigned long long currentFileSize,
        unsigned long long totalFileSize, unsigned int messageInfoIndex,
        unsigned long long currentIndexFileSize, unsigned long long totalIndexFileSize) {
        NSDictionary *messageDic = [self mZIMMessage:message];
        [self sendEventWithName:RN_EVENT(@"messageLoadingProgress")
                           body:@{
                             @"handle" : _handle,
                             @"data" : @[
                               messageDic, @(currentFileSize), @(totalFileSize),
                               @(messageInfoIndex), @(currentIndexFileSize), @(totalIndexFileSize)
                             ]
                           }];
      };

  [zim replyMessage:messageObj
      toOriginalMessage:originMessageObj
                 config:configObj
           notification:notification
               callback:^(ZIMMessage *message, ZIMError *errorInfo) {
                 if ([self isSucc:errorInfo reject:reject]) {
                   NSDictionary *messageDict = [self mZIMMessage:message];
                   resolve(@{@"message" : messageDict});
                 }
               }];
}

RCT_EXPORT_METHOD(queryMessageRepliedList
                  : (NSString *)handle message
                  : (NSDictionary *)message config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessage *messageObj = [self oZIMMessage:message];

  ZIMMessageRepliedListQueryConfig *configObj = [[ZIMMessageRepliedListQueryConfig alloc] init];
  configObj.count = (unsigned int)[RCTConvert uint64_t:config[@"count"]];
  configObj.nextFlag = (unsigned int)[RCTConvert uint64_t:config[@"nextFlag"]];

  [zim queryMessageRepliedListByMessage:messageObj
                                 config:configObj
                               callback:^(NSArray<ZIMMessage *> *messageList, long long nextFlag,
                                          ZIMMessageRootRepliedInfo *rootRepliedInfo,
                                          ZIMError *errorInfo) {
                                 if ([self isSucc:errorInfo reject:reject]) {
                                   NSMutableDictionary *infoMap = [NSMutableDictionary dictionary];
                                   infoMap[@"state"] = @(rootRepliedInfo.state);
                                   infoMap[@"repliedCount"] = @(rootRepliedInfo.repliedCount);
                                   infoMap[@"senderUserID"] = rootRepliedInfo.senderUserID;
                                   infoMap[@"sentTime"] = @(rootRepliedInfo.sentTime);

                                   NSDictionary *msgDict =
                                       [self mZIMMessage:rootRepliedInfo.message];
                                   infoMap[@"message"] = msgDict == nil ? [NSNull null] : msgDict;

                                   resolve(@{
                                     @"nextFlag" : @(nextFlag),
                                     @"rootRepliedInfo" : infoMap,
                                     @"messageList" : [self mZIMMessageList:messageList]
                                   });
                                 }
                               }];
}

RCT_EXPORT_METHOD(queryMessages
                  : (NSString *)handle messageSeqs
                  : (NSArray<NSNumber *> *)messageSeqs conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryMessagesByMessageSeqs:messageSeqs
                   conversationID:conversationID
                 conversationType:conversationType
                         callback:^(NSString *conversationID, ZIMConversationType conversationType,
                                    NSArray<ZIMMessage *> *messageList, ZIMError *errorInfo) {
                           if ([self isSucc:errorInfo reject:reject]) {
                             resolve(@{
                               @"conversationID" : conversationID,
                               @"conversationType" : @(conversationType),
                               @"messageList" : [self mZIMMessageList:messageList]
                             });
                           }
                         }];
}

RCT_EXPORT_METHOD(createRoom
                  : (NSString *)handle roomInfo
                  : (NSDictionary *)roomInfo config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMRoomCreatedCallback callback = ^(ZIMRoomFullInfo *roomInfo, ZIMError *errorInfo) {
    if ([self isSucc:errorInfo reject:reject]) {
      resolve(@{
        @"roomInfo" : @{
          @"baseInfo" :
              @{@"roomID" : roomInfo.baseInfo.roomID, @"roomName" : roomInfo.baseInfo.roomName}
        }
      });
    }
  };

  ZIMRoomInfo *roomInfoObj = [[ZIMRoomInfo alloc] init];
  roomInfoObj.roomID = [RCTConvert NSString:roomInfo[@"roomID"]];
  roomInfoObj.roomName = [RCTConvert NSString:roomInfo[@"roomName"]];

  if (config && ![config isEqual:[NSNull null]]) {
    ZIMRoomAdvancedConfig *configObj = [[ZIMRoomAdvancedConfig alloc] init];
    configObj.roomDestroyDelayTime =
        (unsigned int)[RCTConvert uint64_t:config[@"roomDestroyDelayTime"]];
    NSDictionary *arrtsDict = [RCTConvert NSDictionary:config[@"roomAttributes"]];
    if (arrtsDict && ![arrtsDict isEqual:[NSNull null]]) {
      configObj.roomAttributes = arrtsDict;
    }

    [zim createRoom:roomInfoObj config:configObj callback:callback];
  } else {
    [zim createRoom:roomInfoObj callback:callback];
  }
}

RCT_EXPORT_METHOD(enterRoom
                  : (NSString *)handle roomInfo
                  : (NSDictionary *)roomInfo config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMRoomInfo *roomInfoObj = [[ZIMRoomInfo alloc] init];
  roomInfoObj.roomID = [RCTConvert NSString:roomInfo[@"roomID"]];
  roomInfoObj.roomName = [RCTConvert NSString:roomInfo[@"roomName"]];

  ZIMRoomAdvancedConfig *configObj = [[ZIMRoomAdvancedConfig alloc] init];
  if (config && ![config isEqual:[NSNull null]]) {
    configObj.roomDestroyDelayTime =
        (unsigned int)[RCTConvert uint64_t:config[@"roomDestroyDelayTime"]];
    NSDictionary *arrtsDict = [RCTConvert NSDictionary:config[@"roomAttributes"]];
    if (arrtsDict && ![arrtsDict isEqual:[NSNull null]]) {
      configObj.roomAttributes = arrtsDict;
    }
  }

  [zim
      enterRoom:roomInfoObj
         config:configObj
       callback:^(ZIMRoomFullInfo *roomInfo, ZIMError *errorInfo) {
         if ([self isSucc:errorInfo reject:reject]) {
           resolve(@{
             @"roomInfo" : @{
               @"baseInfo" :
                   @{@"roomID" : roomInfo.baseInfo.roomID, @"roomName" : roomInfo.baseInfo.roomName}
             }
           });
         }
       }];
}

RCT_EXPORT_METHOD(joinRoom
                  : (NSString *)handle roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim joinRoom:roomID
       callback:^(ZIMRoomFullInfo *roomInfo, ZIMError *errorInfo) {
         if ([self isSucc:errorInfo reject:reject]) {
           resolve(@{
             @"roomInfo" : @{
               @"baseInfo" :
                   @{@"roomID" : roomInfo.baseInfo.roomID, @"roomName" : roomInfo.baseInfo.roomName}
             }
           });
         }
       }];
}

RCT_EXPORT_METHOD(switchRoom
                  : (NSString *)handle fromRoomID
                  : (NSString *)fromRoomID toRoomInfo
                  : (NSDictionary *)toRoomInfo isCreateWhenRoomNotExisted
                  : (BOOL)isCreateWhenRoomNotExisted config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }
  ZIMRoomInfo *roomInfoObj = [[ZIMRoomInfo alloc] init];
  roomInfoObj.roomID = [toRoomInfo objectForKey:@"roomID"];
  roomInfoObj.roomName = [toRoomInfo objectForKey:@"roomName"];

  ZIMRoomAdvancedConfig *configObj = [[ZIMRoomAdvancedConfig alloc] init];
  if (config && ![config isEqual:[NSNull null]]) {
    configObj.roomDestroyDelayTime =
        (unsigned int)[RCTConvert uint64_t:config[@"roomDestroyDelayTime"]];
    NSDictionary *arrtsDict = [RCTConvert NSDictionary:config[@"roomAttributes"]];
    if (arrtsDict && ![arrtsDict isEqual:[NSNull null]]) {
      configObj.roomAttributes = arrtsDict;
    }
  }

  [zim switchRoomFromRoomID:fromRoomID
                      toRoomInfo:roomInfoObj
      isCreateWhenRoomNotExisted:isCreateWhenRoomNotExisted
                          config:configObj
                        callback:^(ZIMRoomFullInfo *roomInfo, ZIMError *errorInfo) {
                          if ([self isSucc:errorInfo reject:reject]) {
                            resolve(@{
                              @"roomInfo" : @{
                                @"baseInfo" : @{
                                  @"roomID" : roomInfo.baseInfo.roomID,
                                  @"roomName" : roomInfo.baseInfo.roomName
                                }
                              }
                            });
                          }
                        }];
}

RCT_EXPORT_METHOD(leaveRoom
                  : (NSString *)handle roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim leaveRoom:roomID
        callback:^(NSString *roomID, ZIMError *errorInfo) {
          if ([self isSucc:errorInfo reject:reject]) {
            resolve(@{@"roomID" : roomID});
          }
        }];
}

RCT_EXPORT_METHOD(leaveAllRoom
                  : (NSString *)handle resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim leaveAllRoom:^(NSArray<NSString *> *roomIDs, ZIMError *errorInfo) {
    if ([self isSucc:errorInfo reject:reject]) {
      resolve(@{@"roomIDs" : roomIDs});
    }
  }];
}

RCT_EXPORT_METHOD(queryRoomMemberList
                  : (NSString *)handle roomID
                  : (NSString *)roomID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMRoomMemberQueryConfig *configObj = [[ZIMRoomMemberQueryConfig alloc] init];
  configObj.nextFlag = [RCTConvert NSString:config[@"nextFlag"]];
  configObj.count = (unsigned int)[RCTConvert uint64_t:config[@"count"]];

  [zim queryRoomMemberListByRoomID:roomID
                            config:configObj
                          callback:^(NSString *roomID, NSArray<ZIMUserInfo *> *memberList,
                                     NSString *nextFlag, ZIMError *errorInfo) {
                            if ([self isSucc:errorInfo reject:reject]) {
                              resolve(@{
                                @"roomID" : roomID,
                                @"memberList" : [self mZIMUserInfoList:memberList],
                                @"nextFlag" : nextFlag
                              });
                            }
                          }];
}

RCT_EXPORT_METHOD(queryRoomMembers
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim
      queryRoomMembersByUserIDs:userIDs
                         roomID:roomID
                       callback:^(NSString *roomID, NSArray<ZIMRoomMemberInfo *> *memberList,
                                  NSArray<ZIMErrorUserInfo *> *errorUserList, ZIMError *errorInfo) {
                         if ([self isSucc:errorInfo reject:reject]) {
                           resolve(@{
                             @"roomID" : roomID,
                             @"memberList" : [self mZIMUserInfoList:memberList],
                             @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                           });
                         }
                       }];
}

RCT_EXPORT_METHOD(queryRoomOnlineMemberCount
                  : (NSString *)handle roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryRoomOnlineMemberCountByRoomID:roomID
                                 callback:^(NSString *roomID, unsigned int count,
                                            ZIMError *errorInfo) {
                                   if ([self isSucc:errorInfo reject:reject]) {
                                     resolve(@{@"roomID" : roomID, @"count" : @(count)});
                                   }
                                 }];
}

RCT_EXPORT_METHOD(queryRoomAllAttributes
                  : (NSString *)handle roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryRoomAllAttributesByRoomID:roomID
                             callback:^(NSString *roomID,
                                        NSDictionary<NSString *, NSString *> *roomAttributes,
                                        ZIMError *errorInfo) {
                               if ([self isSucc:errorInfo reject:reject]) {
                                 resolve(@{@"roomID" : roomID, @"roomAttributes" : roomAttributes});
                               }
                             }];
}

RCT_EXPORT_METHOD(setRoomAttributes
                  : (NSString *)handle roomAttributes
                  : (NSDictionary *)roomAttributes roomID
                  : (NSString *)roomID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMRoomAttributesSetConfig *configObj = [[ZIMRoomAttributesSetConfig alloc] init];
  configObj.isForce = [RCTConvert BOOL:config[@"isForce"]];
  configObj.isUpdateOwner = [RCTConvert BOOL:config[@"isUpdateOwner"]];
  configObj.isDeleteAfterOwnerLeft = [RCTConvert BOOL:config[@"isDeleteAfterOwnerLeft"]];

  [zim setRoomAttributes:roomAttributes
                  roomID:roomID
                  config:configObj
                callback:^(NSString *roomID, NSArray<NSString *> *errorKeys, ZIMError *errorInfo) {
                  if ([self isSucc:errorInfo reject:reject]) {
                    resolve(@{@"roomID" : roomID, @"errorKeys" : errorKeys});
                  }
                }];
}

RCT_EXPORT_METHOD(deleteRoomAttributes
                  : (NSString *)handle keys
                  : (NSArray *)keys roomID
                  : (NSString *)roomID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMRoomAttributesDeleteConfig *configObj = [[ZIMRoomAttributesDeleteConfig alloc] init];
  configObj.isForce = [RCTConvert BOOL:config[@"isForce"]];

  [zim deleteRoomAttributesByKeys:keys
                           roomID:roomID
                           config:configObj
                         callback:^(NSString *roomID, NSArray<NSString *> *errorKeys,
                                    ZIMError *errorInfo) {
                           if ([self isSucc:errorInfo reject:reject]) {
                             resolve(@{@"roomID" : roomID, @"errorKeys" : errorKeys});
                           }
                         }];
}

RCT_EXPORT_METHOD(beginRoomAttributesBatchOperation
                  : (NSString *)handle roomID
                  : (NSString *)roomID config
                  : (NSDictionary *)config) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    return;
  }

  ZIMRoomAttributesBatchOperationConfig *configObj =
      [[ZIMRoomAttributesBatchOperationConfig alloc] init];
  configObj.isForce = [RCTConvert BOOL:config[@"isForce"]];
  configObj.isUpdateOwner = [RCTConvert BOOL:config[@"isUpdateOwner"]];
  configObj.isDeleteAfterOwnerLeft = [RCTConvert BOOL:config[@"isDeleteAfterOwnerLeft"]];

  [zim beginRoomAttributesBatchOperationWithRoomID:roomID config:configObj];
}

RCT_EXPORT_METHOD(endRoomAttributesBatchOperation
                  : (NSString *)handle roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim endRoomAttributesBatchOperationWithRoomID:roomID
                                        callback:^(NSString *roomID, ZIMError *errorInfo) {
                                          if ([self isSucc:errorInfo reject:reject]) {
                                            resolve(@{@"roomID" : roomID});
                                          }
                                        }];
}

RCT_EXPORT_METHOD(setRoomMembersAttributes
                  : (NSString *)handle attributes
                  : (NSDictionary *)attributes userIDs
                  : (NSArray<NSString *> *)userIDs roomID
                  : (NSString *)roomID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMRoomMemberAttributesSetConfig *configObj = [[ZIMRoomMemberAttributesSetConfig alloc] init];
  configObj.isDeleteAfterOwnerLeft = [RCTConvert BOOL:config[@"isDeleteAfterOwnerLeft"]];

  [zim setRoomMembersAttributes:attributes
                        userIDs:userIDs
                         roomID:roomID
                         config:configObj
                       callback:^(NSString *roomID,
                                  NSArray<ZIMRoomMemberAttributesOperatedInfo *> *infos,
                                  NSArray<NSString *> *errorUserList, ZIMError *errorInfo) {
                         if ([self isSucc:errorInfo reject:reject]) {
                           resolve(@{
                             @"roomID" : roomID,
                             @"infos" : [self mZIMRoomMemberAttributesOperatedInfoList:infos],
                             @"errorUserList" : errorUserList
                           });
                         }
                       }];
}

RCT_EXPORT_METHOD(queryRoomMembersAttributes
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryRoomMembersAttributesByUserIDs:userIDs
                                    roomID:roomID
                                  callback:^(NSString *roomID,
                                             NSArray<ZIMRoomMemberAttributesInfo *> *infos,
                                             ZIMError *errorInfo) {
                                    if ([self isSucc:errorInfo reject:reject]) {
                                      resolve(@{
                                        @"roomID" : roomID,
                                        @"infos" : [self mZIMRoomMemberAttributesInfoList:infos]
                                      });
                                    }
                                  }];
}

RCT_EXPORT_METHOD(queryRoomMemberAttributesList
                  : (NSString *)handle roomID
                  : (NSString *)roomID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMRoomMemberAttributesQueryConfig *configObj = [[ZIMRoomMemberAttributesQueryConfig alloc] init];
  configObj.nextFlag = [RCTConvert NSString:config[@"nextFlag"]];
  configObj.count = (unsigned int)[RCTConvert uint64_t:config[@"count"]];

  [zim queryRoomMemberAttributesListByRoomID:roomID
                                      config:configObj
                                    callback:^(NSString *roomID,
                                               NSArray<ZIMRoomMemberAttributesInfo *> *infos,
                                               NSString *nextFlag, ZIMError *errorInfo) {
                                      if ([self isSucc:errorInfo reject:reject]) {
                                        resolve(@{
                                          @"roomID" : roomID,
                                          @"infos" : [self mZIMRoomMemberAttributesInfoList:infos],
                                          @"nextFlag" : nextFlag
                                        });
                                      }
                                    }];
}

RCT_EXPORT_METHOD(createGroup
                  : (NSString *)handle groupInfo
                  : (NSDictionary *)groupInfo userIDs
                  : (NSArray<NSString *> *)userIDs config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupCreatedCallback callback =
      ^(ZIMGroupFullInfo *groupInfo, NSArray<ZIMGroupMemberInfo *> *userList,
        NSArray<ZIMErrorUserInfo *> *errorUserList, ZIMError *errorInfo) {
        if ([self isSucc:errorInfo reject:reject]) {
          resolve(@{
            @"groupInfo" : [self mZIMGroupFullInfo:groupInfo],
            @"userList" : [self mZIMGroupMemberInfoList:userList],
            @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
          });
        }
      };

  ZIMGroupInfo *groupInfoObj = [[ZIMGroupInfo alloc] init];
  groupInfoObj.groupID = [RCTConvert NSString:groupInfo[@"groupID"]];
  groupInfoObj.groupName = [RCTConvert NSString:groupInfo[@"groupName"]];
  groupInfoObj.groupAvatarUrl = [RCTConvert NSString:groupInfo[@"groupAvatarUrl"]];

  if (config && ![config isEqual:[NSNull null]]) {
    ZIMGroupAdvancedConfig *configObj = [[ZIMGroupAdvancedConfig alloc] init];

    configObj.groupNotice = [RCTConvert NSString:config[@"groupNotice"]];
    configObj.maxMemberCount = [RCTConvert int:config[@"maxMemberCount"]];
    configObj.joinMode = (ZIMGroupJoinMode)[RCTConvert int:config[@"joinMode"]];
    configObj.inviteMode = (ZIMGroupInviteMode)[RCTConvert int:config[@"inviteMode"]];
    configObj.beInviteMode = (ZIMGroupBeInviteMode)[RCTConvert int:config[@"beInviteMode"]];

    NSDictionary *arrtsDict = [RCTConvert NSDictionary:config[@"groupAttributes"]];
    if (arrtsDict && ![arrtsDict isEqual:[NSNull null]]) {
      configObj.groupAttributes = arrtsDict;
    }

    [zim createGroup:groupInfoObj userIDs:userIDs config:configObj callback:callback];
  } else {
    [zim createGroup:groupInfoObj userIDs:userIDs callback:callback];
  }
}

RCT_EXPORT_METHOD(joinGroup
                  : (NSString *)handle groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim joinGroup:groupID
        callback:^(ZIMGroupFullInfo *groupInfo, ZIMError *errorInfo) {
          if ([self isSucc:errorInfo reject:reject]) {
            resolve(@{@"groupInfo" : [self mZIMGroupFullInfo:groupInfo]});
          }
        }];
}

RCT_EXPORT_METHOD(leaveGroup
                  : (NSString *)handle groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim leaveGroup:groupID
         callback:^(NSString *groupID, ZIMError *errorInfo) {
           if ([self isSucc:errorInfo reject:reject]) {
             resolve(@{@"groupID" : groupID});
           }
         }];
}

RCT_EXPORT_METHOD(dismissGroup
                  : (NSString *)handle groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim dismissGroup:groupID
           callback:^(NSString *groupID, ZIMError *errorInfo) {
             if ([self isSucc:errorInfo reject:reject]) {
               resolve(@{@"groupID" : groupID});
             }
           }];
}

RCT_EXPORT_METHOD(queryGroupList
                  : (NSString *)handle count
                  : (int)count config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupListQueriedCallback callback =
      ^(NSArray<ZIMGroup *> *groupList, long long nextFlag, ZIMError *errorInfo) {
        if ([self isSucc:errorInfo reject:reject]) {
          resolve(@{@"nextFlag" : @(nextFlag), @"groupList" : [self mZIMGroupList:groupList]});
        }
      };

  if (!config || [config isEqual:[NSNull null]]) {
    [zim queryGroupList:callback];
  } else {
    ZIMGroupListQueryConfig *configObj = [[ZIMGroupListQueryConfig alloc] init];
    configObj.nextFlag = (unsigned int)[RCTConvert uint64_t:config[@"nextFlag"]];

    NSMutableArray *userIDsArray = config[@"userIDs"];
    if (userIDsArray != nil) {
      configObj.userIDs = userIDsArray;
    }

    [zim queryGroupListWithCount:(unsigned int)count config:configObj callback:callback];
  }
}

RCT_EXPORT_METHOD(updateGroupNotice
                  : (NSString *)handle groupNotice
                  : (NSString *)groupNotice groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateGroupNotice:groupNotice
                 groupID:groupID
                callback:^(NSString *groupID, NSString *groupNotice, ZIMError *errorInfo) {
                  if ([self isSucc:errorInfo reject:reject]) {
                    resolve(@{@"groupID" : groupID, @"groupNotice" : groupNotice});
                  }
                }];
}

RCT_EXPORT_METHOD(updateGroupName
                  : (NSString *)handle groupName
                  : (NSString *)groupName groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateGroupName:groupName
               groupID:groupID
              callback:^(NSString *groupID, NSString *groupName, ZIMError *errorInfo) {
                if ([self isSucc:errorInfo reject:reject]) {
                  resolve(@{@"groupID" : groupID, @"groupName" : groupName});
                }
              }];
}

RCT_EXPORT_METHOD(updateGroupAvatarUrl
                  : (NSString *)handle groupAvatarUrl
                  : (NSString *)groupAvatarUrl groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateGroupAvatarUrl:groupAvatarUrl
                    groupID:groupID
                   callback:^(NSString *groupID, NSString *groupAvatarUrl, ZIMError *errorInfo) {
                     if ([self isSucc:errorInfo reject:reject]) {
                       resolve(@{@"groupID" : groupID, @"groupAvatarUrl" : groupAvatarUrl});
                     }
                   }];
}

RCT_EXPORT_METHOD(updateGroupAlias
                  : (NSString *)handle groupAlias
                  : (NSString *)groupAlias groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }
  [zim updateGroupAlias:groupAlias
                groupID:groupID
               callback:^(NSString *groupID, NSString *groupAlias, ZIMError *errorInfo) {
                 if ([self isSucc:errorInfo reject:reject]) {
                   resolve(@{@"groupID" : groupID, @"groupAlias" : groupAlias});
                 }
               }];
}

RCT_EXPORT_METHOD(muteGroup
                  : (NSString *)handle isMute
                  : (BOOL)isMute groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupMuteConfig *configObj = [[ZIMGroupMuteConfig alloc] init];
  configObj.mode = (ZIMGroupMuteMode)[RCTConvert int:config[@"mode"]];
  configObj.duration = [RCTConvert int:config[@"duration"]];
  NSMutableArray *rolesArray = config[@"roles"];
  if (rolesArray != nil) {
    configObj.roles = rolesArray;
  }

  [zim
      muteGroup:isMute
        groupID:groupID
         config:configObj
       callback:^(NSString *groupID, BOOL isMute, ZIMGroupMuteInfo *muteInfo, ZIMError *errorInfo) {
         if ([self isSucc:errorInfo reject:reject]) {
           resolve(@{
             @"groupID" : groupID,
             @"isMute" : [NSNumber numberWithBool:isMute],
             @"mutedInfo" : [self mZIMGroupMuteInfo:muteInfo]
           });
         }
       }];
}

RCT_EXPORT_METHOD(queryGroupInfo
                  : (NSString *)handle groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryGroupInfoByGroupID:groupID
                      callback:^(ZIMGroupFullInfo *groupInfo, ZIMError *errorInfo) {
                        if ([self isSucc:errorInfo reject:reject]) {
                          resolve(@{@"groupInfo" : [self mZIMGroupFullInfo:groupInfo]});
                        }
                      }];
}

RCT_EXPORT_METHOD(queryGroupMemberCount
                  : (NSString *)handle groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim
      queryGroupMemberCountByGroupID:groupID
                            callback:^(NSString *groupID, unsigned int count, ZIMError *errorInfo) {
                              if ([self isSucc:errorInfo reject:reject]) {
                                resolve(@{@"groupID" : groupID, @"count" : @(count)});
                              }
                            }];
}

RCT_EXPORT_METHOD(muteGroupMembers
                  : (NSString *)handle isMute
                  : (BOOL)isMute userIDs
                  : (NSArray<NSString *> *)userIDs groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupMemberMuteConfig *configObj = [[ZIMGroupMemberMuteConfig alloc] init];
  configObj.duration = [RCTConvert int:config[@"duration"]];

  [zim muteGroupMembers:isMute
                userIDs:userIDs
                groupID:groupID
                 config:configObj
               callback:^(NSString *groupID, BOOL isMute, int duration, NSArray<NSString *> *users,
                          NSArray<ZIMErrorUserInfo *> *errorUserList, ZIMError *errorInfo) {
                 if ([self isSucc:errorInfo reject:reject]) {
                   resolve(@{
                     @"groupID" : groupID,
                     @"isMute" : [NSNumber numberWithBool:isMute],
                     @"duration" : @(duration),
                     @"mutedUserIDs" : users,
                     @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                   });
                 }
               }];
}

RCT_EXPORT_METHOD(setGroupAttributes
                  : (NSString *)handle groupAttributes
                  : (NSDictionary *)groupAttributes groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim
      setGroupAttributes:groupAttributes
                 groupID:groupID
                callback:^(NSString *groupID, NSArray<NSString *> *errorKeys, ZIMError *errorInfo) {
                  if ([self isSucc:errorInfo reject:reject]) {
                    resolve(@{@"groupID" : groupID, @"errorKeys" : errorKeys});
                  }
                }];
}

RCT_EXPORT_METHOD(deleteGroupAttributes
                  : (NSString *)handle keys
                  : (NSArray<NSString *> *)keys groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim deleteGroupAttributesByKeys:keys
                           groupID:groupID
                          callback:^(NSString *groupID, NSArray<NSString *> *errorKeys,
                                     ZIMError *errorInfo) {
                            if ([self isSucc:errorInfo reject:reject]) {
                              resolve(@{@"groupID" : groupID, @"errorKeys" : errorKeys});
                            }
                          }];
}

RCT_EXPORT_METHOD(queryGroupAttributes
                  : (NSString *)handle keys
                  : (NSArray<NSString *> *)keys groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryGroupAttributesByKeys:keys
                          groupID:groupID
                         callback:^(NSString *groupID,
                                    NSDictionary<NSString *, NSString *> *groupAttributes,
                                    ZIMError *errorInfo) {
                           if ([self isSucc:errorInfo reject:reject]) {
                             resolve(@{@"groupID" : groupID, @"groupAttributes" : groupAttributes});
                           }
                         }];
}

RCT_EXPORT_METHOD(queryGroupAllAttributes
                  : (NSString *)handle groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryGroupAllAttributesByGroupID:groupID
                               callback:^(NSString *groupID,
                                          NSDictionary<NSString *, NSString *> *groupAttributes,
                                          ZIMError *errorInfo) {
                                 if ([self isSucc:errorInfo reject:reject]) {
                                   resolve(@{
                                     @"groupID" : groupID,
                                     @"groupAttributes" : groupAttributes
                                   });
                                 }
                               }];
}

RCT_EXPORT_METHOD(setGroupMemberNickname
                  : (NSString *)handle nickname
                  : (NSString *)nickname forUserID
                  : (NSString *)forUserID groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim setGroupMemberNickname:nickname
                    forUserID:forUserID
                      groupID:groupID
                     callback:^(NSString *groupID, NSString *forUserID, NSString *nickname,
                                ZIMError *errorInfo) {
                       if ([self isSucc:errorInfo reject:reject]) {
                         resolve(@{
                           @"groupID" : groupID,
                           @"forUserID" : forUserID,
                           @"nickname" : nickname
                         });
                       }
                     }];
}

RCT_EXPORT_METHOD(setGroupMemberRole
                  : (NSString *)handle role
                  : (int)role forUserID
                  : (NSString *)forUserID groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim setGroupMemberRole:role
                forUserID:forUserID
                  groupID:groupID
                 callback:^(NSString *groupID, NSString *forUserID, ZIMGroupMemberRole role,
                            ZIMError *errorInfo) {
                   if ([self isSucc:errorInfo reject:reject]) {
                     resolve(@{@"groupID" : groupID, @"forUserID" : forUserID, @"role" : @(role)});
                   }
                 }];
}

RCT_EXPORT_METHOD(transferGroupOwner
                  : (NSString *)handle toUserID
                  : (NSString *)toUserID groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim transferGroupOwnerToUserID:toUserID
                          groupID:groupID
                         callback:^(NSString *groupID, NSString *toUserID, ZIMError *errorInfo) {
                           if ([self isSucc:errorInfo reject:reject]) {
                             resolve(@{@"groupID" : groupID, @"toUserID" : toUserID});
                           }
                         }];
}

RCT_EXPORT_METHOD(queryGroupMemberInfo
                  : (NSString *)handle userID
                  : (NSString *)userID groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryGroupMemberInfoByUserID:userID
                            groupID:groupID
                           callback:^(NSString *groupID, ZIMGroupMemberInfo *userInfo,
                                      ZIMError *errorInfo) {
                             if ([self isSucc:errorInfo reject:reject]) {
                               resolve(@{
                                 @"groupID" : groupID,
                                 @"userInfo" : [self mZIMGroupMemberInfo:userInfo]
                               });
                             }
                           }];
}

RCT_EXPORT_METHOD(inviteUsersIntoGroup
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim inviteUsersIntoGroup:userIDs
                    groupID:groupID
                   callback:^(NSString *groupID, NSArray<ZIMGroupMemberInfo *> *userList,
                              NSArray<ZIMErrorUserInfo *> *errorUserList, ZIMError *errorInfo) {
                     if ([self isSucc:errorInfo reject:reject]) {
                       resolve(@{
                         @"groupID" : groupID,
                         @"userList" : [self mZIMGroupMemberInfoList:userList],
                         @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                       });
                     }
                   }];
}

RCT_EXPORT_METHOD(kickGroupMembers
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim kickGroupMembers:userIDs
                groupID:groupID
               callback:^(NSString *groupID, NSArray<NSString *> *kickedUserIDList,
                          NSArray<ZIMErrorUserInfo *> *errorUserList, ZIMError *errorInfo) {
                 if ([self isSucc:errorInfo reject:reject]) {
                   resolve(@{
                     @"groupID" : groupID,
                     @"kickedUserIDs" : kickedUserIDList,
                     @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                   });
                 }
               }];
}

RCT_EXPORT_METHOD(queryGroupMemberList
                  : (NSString *)handle groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupMemberQueryConfig *configObj = [[ZIMGroupMemberQueryConfig alloc] init];
  configObj.count = (unsigned int)[RCTConvert uint64_t:config[@"count"]];
  configObj.nextFlag = (unsigned int)[RCTConvert uint64_t:config[@"nextFlag"]];

  [zim queryGroupMemberListByGroupID:groupID
                              config:configObj
                            callback:^(NSString *groupID, NSArray<ZIMGroupMemberInfo *> *userList,
                                       unsigned int nextFlag, ZIMError *errorInfo) {
                              if ([self isSucc:errorInfo reject:reject]) {
                                resolve(@{
                                  @"groupID" : groupID,
                                  @"userList" : [self mZIMGroupMemberInfoList:userList],
                                  @"nextFlag" : @(nextFlag)
                                });
                              }
                            }];
}

RCT_EXPORT_METHOD(queryGroupMemberMutedList
                  : (NSString *)handle groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupMemberMutedListQueryConfig *configObj = [[ZIMGroupMemberMutedListQueryConfig alloc] init];
  configObj.count = [RCTConvert int:config[@"count"]];
  configObj.nextFlag = [RCTConvert int:config[@"nextFlag"]];

  [zim queryGroupMemberMutedListByGroupID:groupID
                                   config:configObj
                                 callback:^(NSString *groupID, unsigned long long nextFlag,
                                            NSArray<ZIMGroupMemberInfo *> *userList,
                                            ZIMError *errorInfo) {
                                   if ([self isSucc:errorInfo reject:reject]) {
                                     resolve(@{
                                       @"groupID" : groupID,
                                       @"userList" : [self mZIMGroupMemberInfoList:userList],
                                       @"nextFlag" : @(nextFlag)
                                     });
                                   }
                                 }];
}

RCT_EXPORT_METHOD(updateGroupJoinMode
                  : (NSString *)handle mode
                  : (int)mode groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateGroupJoinMode:(ZIMGroupJoinMode)mode
                   groupID:groupID
                  callback:^(NSString *groupID, ZIMGroupJoinMode mode, ZIMError *errorInfo) {
                    if ([self isSucc:errorInfo reject:reject]) {
                      resolve(@{@"groupID" : groupID, @"mode" : @(mode)});
                    }
                  }];
}

RCT_EXPORT_METHOD(updateGroupInviteMode
                  : (NSString *)handle mode
                  : (int)mode groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateGroupInviteMode:(ZIMGroupInviteMode)mode
                     groupID:groupID
                    callback:^(NSString *groupID, ZIMGroupInviteMode mode, ZIMError *errorInfo) {
                      if ([self isSucc:errorInfo reject:reject]) {
                        resolve(@{@"groupID" : groupID, @"mode" : @(mode)});
                      }
                    }];
}

RCT_EXPORT_METHOD(updateGroupBeInviteMode
                  : (NSString *)handle mode
                  : (int)mode groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim
      updateGroupBeInviteMode:(ZIMGroupBeInviteMode)mode
                      groupID:groupID
                     callback:^(NSString *groupID, ZIMGroupBeInviteMode mode, ZIMError *errorInfo) {
                       if ([self isSucc:errorInfo reject:reject]) {
                         resolve(@{@"groupID" : groupID, @"mode" : @(mode)});
                       }
                     }];
}

RCT_EXPORT_METHOD(sendGroupJoinApplication
                  : (NSString *)handle groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupJoinApplicationSendConfig *configObj = [[ZIMGroupJoinApplicationSendConfig alloc] init];
  configObj.wording = [RCTConvert NSString:config[@"wording"]];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim sendGroupJoinApplicationToGroupID:groupID
                                  config:configObj
                                callback:^(NSString *groupID, ZIMError *errorInfo) {
                                  if ([self isSucc:errorInfo reject:reject]) {
                                    resolve(@{@"groupID" : groupID});
                                  }
                                }];
}

RCT_EXPORT_METHOD(acceptGroupJoinApplication
                  : (NSString *)handle userID
                  : (NSString *)userID groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupJoinApplicationAcceptConfig *configObj =
      [[ZIMGroupJoinApplicationAcceptConfig alloc] init];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim acceptGroupJoinApplicationFromUserID:userID
                                    groupID:groupID
                                     config:configObj
                                   callback:^(NSString *groupID, NSString *userID,
                                              ZIMError *errorInfo) {
                                     if ([self isSucc:errorInfo reject:reject]) {
                                       resolve(@{@"groupID" : groupID, @"userID" : userID});
                                     }
                                   }];
}

RCT_EXPORT_METHOD(rejectGroupJoinApplication
                  : (NSString *)handle userID
                  : (NSString *)userID groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupJoinApplicationRejectConfig *configObj =
      [[ZIMGroupJoinApplicationRejectConfig alloc] init];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim rejectGroupJoinApplicationFromUserID:userID
                                    groupID:groupID
                                     config:configObj
                                   callback:^(NSString *groupID, NSString *userID,
                                              ZIMError *errorInfo) {
                                     if ([self isSucc:errorInfo reject:reject]) {
                                       resolve(@{@"groupID" : groupID, @"userID" : userID});
                                     }
                                   }];
}

RCT_EXPORT_METHOD(sendGroupInviteApplications
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupInviteApplicationSendConfig *configObj =
      [[ZIMGroupInviteApplicationSendConfig alloc] init];
  configObj.wording = [RCTConvert NSString:config[@"wording"]];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim sendGroupInviteApplicationsToUserIDs:userIDs
                                    groupID:groupID
                                     config:configObj
                                   callback:^(NSString *groupID,
                                              NSArray<ZIMErrorUserInfo *> *errorUserList,
                                              ZIMError *errorInfo) {
                                     if ([self isSucc:errorInfo reject:reject]) {
                                       resolve(@{
                                         @"groupID" : groupID,
                                         @"errorUserList" :
                                             [self mZIMErrorUserInfoList:errorUserList]
                                       });
                                     }
                                   }];
}

RCT_EXPORT_METHOD(acceptGroupInviteApplication
                  : (NSString *)handle userID
                  : (NSString *)userID groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupInviteApplicationAcceptConfig *configObj =
      [[ZIMGroupInviteApplicationAcceptConfig alloc] init];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim acceptGroupInviteApplicationFromInviterUserID:userID
                                             groupID:groupID
                                              config:configObj
                                            callback:^(ZIMGroupFullInfo *groupInfo,
                                                       NSString *userID, ZIMError *errorInfo) {
                                              if ([self isSucc:errorInfo reject:reject]) {
                                                resolve(@{
                                                  @"groupInfo" : [self mZIMGroupFullInfo:groupInfo],
                                                  @"inviterUserID" : userID
                                                });
                                              }
                                            }];
}

RCT_EXPORT_METHOD(rejectGroupInviteApplication
                  : (NSString *)handle userID
                  : (NSString *)userID groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupInviteApplicationRejectConfig *configObj =
      [[ZIMGroupInviteApplicationRejectConfig alloc] init];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim rejectGroupInviteApplicationFromInviterUserID:userID
                                             groupID:groupID
                                              config:configObj
                                            callback:^(NSString *groupID, NSString *userID,
                                                       ZIMError *errorInfo) {
                                              if ([self isSucc:errorInfo reject:reject]) {
                                                resolve(@{
                                                  @"groupID" : groupID,
                                                  @"inviterUserID" : userID
                                                });
                                              }
                                            }];
}

RCT_EXPORT_METHOD(queryGroupApplicationList
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupApplicationListQueryConfig *configObj = [[ZIMGroupApplicationListQueryConfig alloc] init];
  configObj.count = [RCTConvert int:config[@"count"]];
  configObj.nextFlag = [RCTConvert int:config[@"nextFlag"]];

  [zim queryGroupApplicationListWithConfig:configObj
                                  callback:^(NSArray<ZIMGroupApplicationInfo *> *infos,
                                             unsigned long long nextFlag, ZIMError *errorInfo) {
                                    if ([self isSucc:errorInfo reject:reject]) {
                                      resolve(@{
                                        @"nextFlag" : @(nextFlag),
                                        @"applicationList" : [self mZIMGroupApplicationList:infos]
                                      });
                                    }
                                  }];
}

RCT_EXPORT_METHOD(createCommunity
                  : (NSString *)handle communityInfo
                  : (NSDictionary *)communityInfo config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCommunityInfo *communityInfoObj = [[ZIMCommunityInfo alloc] init];
  communityInfoObj.communityID = [RCTConvert NSString:communityInfo[@"communityID"]];
  communityInfoObj.communityName = [RCTConvert NSString:communityInfo[@"communityName"]];
  communityInfoObj.communityAvatarUrl = [RCTConvert NSString:communityInfo[@"communityAvatarUrl"]];

  ZIMCommunityCreateConfig *configObj = [[ZIMCommunityCreateConfig alloc] init];
  if (config && ![config isEqual:[NSNull null]]) {
    configObj.communityNotice = [RCTConvert NSString:config[@"communityNotice"]];

    NSDictionary *arrtsDict = [RCTConvert NSDictionary:config[@"communityAttributes"]];
    if (arrtsDict && ![arrtsDict isEqual:[NSNull null]]) {
      configObj.communityAttributes = arrtsDict;
    }
  }

  [zim
      createCommunityWithCommunityInfo:communityInfoObj
                                config:configObj
                              callback:^(ZIMCommunityFullInfo *communityInfo, ZIMError *errorInfo) {
                                if ([self isSucc:errorInfo reject:reject]) {
                                  resolve(@{
                                    @"communityInfo" : [self mZIMCommunityFullInfo:communityInfo]
                                  });
                                }
                              }];
}

RCT_EXPORT_METHOD(dismissCommunity
                  : (NSString *)handle communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim dismissCommunityByCommunityID:communityID
                            callback:^(NSString *communityID, ZIMError *errorInfo) {
                              if ([self isSucc:errorInfo reject:reject]) {
                                resolve(@{@"communityID" : communityID});
                              }
                            }];
}

RCT_EXPORT_METHOD(joinCommunity
                  : (NSString *)handle communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim
      joinCommunityWithCommunityID:communityID
                          callback:^(ZIMCommunityFullInfo *communityInfo, ZIMError *errorInfo) {
                            if ([self isSucc:errorInfo reject:reject]) {
                              resolve(
                                  @{@"communityInfo" : [self mZIMCommunityFullInfo:communityInfo]});
                            }
                          }];
}

RCT_EXPORT_METHOD(leaveCommunity
                  : (NSString *)handle communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim leaveCommunityByCommunityID:communityID
                          callback:^(NSString *communityID, ZIMError *errorInfo) {
                            if ([self isSucc:errorInfo reject:reject]) {
                              resolve(@{@"communityID" : communityID});
                            }
                          }];
}

RCT_EXPORT_METHOD(inviteUsersIntoCommunity
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim
      inviteUsersIntoCommunityWithUserIDs:userIDs
                              communityID:communityID
                                 callback:^(NSString *communityID,
                                            NSArray<ZIMErrorUserInfo *> *errorUserList,
                                            ZIMError *errorInfo) {
                                   if ([self isSucc:errorInfo reject:reject]) {
                                     resolve(@{
                                       @"communityID" : communityID,
                                       @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                                     });
                                   }
                                 }];
}

RCT_EXPORT_METHOD(kickCommunityMembers
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim kickCommunityMembersByUserIDs:userIDs
                         communityID:communityID
                            callback:^(NSString *communityID,
                                       NSArray<ZIMErrorUserInfo *> *errorUserList,
                                       ZIMError *errorInfo) {
                              if ([self isSucc:errorInfo reject:reject]) {
                                resolve(@{
                                  @"communityID" : communityID,
                                  @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                                });
                              }
                            }];
}

RCT_EXPORT_METHOD(queryCommunityList
                  : (NSString *)handle count
                  : (int)count config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCommunityListQueryConfig *configObj = [[ZIMCommunityListQueryConfig alloc] init];
  if (config && ![config isEqual:[NSNull null]]) {
    configObj.nextFlag = (unsigned int)[RCTConvert uint64_t:config[@"nextFlag"]];
  }

  [zim queryCommunityListWithCount:(unsigned int)count
                            config:configObj
                          callback:^(NSArray<ZIMCommunity *> *communityList, long long nextFlag,
                                     ZIMError *errorInfo) {
                            if ([self isSucc:errorInfo reject:reject]) {
                              NSMutableArray *infos = [NSMutableArray array];
                              for (ZIMCommunity *info in communityList) {
                                [infos addObject:[self mZIMCommunity:info]];
                              }

                              resolve(@{
                                @"nextFlag" : @(nextFlag),
                                @"communityList" : infos,
                              });
                            }
                          }];
}

RCT_EXPORT_METHOD(updateCommunityName
                  : (NSString *)handle communityName
                  : (NSString *)communityName communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateCommunityName:communityName
               communityID:communityID
                  callback:^(NSString *communityID, NSString *communityName, ZIMError *errorInfo) {
                    if ([self isSucc:errorInfo reject:reject]) {
                      resolve(@{@"communityID" : communityID, @"communityName" : communityName});
                    }
                  }];
}

RCT_EXPORT_METHOD(updateCommunityAvatarUrl
                  : (NSString *)handle communityAvatarUrl
                  : (NSString *)communityAvatarUrl communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateCommunityAvatarUrl:communityAvatarUrl
                    communityID:communityID
                       callback:^(NSString *communityID, NSString *communityAvatarUrl,
                                  ZIMError *errorInfo) {
                         if ([self isSucc:errorInfo reject:reject]) {
                           resolve(@{
                             @"communityID" : communityID,
                             @"communityAvatarUrl" : communityAvatarUrl
                           });
                         }
                       }];
}

RCT_EXPORT_METHOD(updateCommunityNotice
                  : (NSString *)handle communityNotice
                  : (NSString *)communityNotice communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateCommunityNotice:communityNotice
                 communityID:communityID
                    callback:^(NSString *communityID, NSString *communityNotice,
                               ZIMError *errorInfo) {
                      if ([self isSucc:errorInfo reject:reject]) {
                        resolve(
                            @{@"communityID" : communityID, @"communityNotice" : communityNotice});
                      }
                    }];
}

RCT_EXPORT_METHOD(setCommunityAttributes
                  : (NSString *)handle communityAttributes
                  : (NSDictionary *)communityAttributes communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim setCommunityAttributes:communityAttributes
                  communityID:communityID
                     callback:^(NSString *communityID, NSArray<NSString *> *errorKeys,
                                ZIMError *errorInfo) {
                       if ([self isSucc:errorInfo reject:reject]) {
                         resolve(@{@"communityID" : communityID, @"errorKeys" : errorKeys});
                       }
                     }];
}

RCT_EXPORT_METHOD(deleteCommunityAttributes
                  : (NSString *)handle keys
                  : (NSArray<NSString *> *)keys communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim
      deleteCommunityAttributesByKeys:keys
                          communityID:communityID
                             callback:^(NSString *communityID, NSArray<NSString *> *errorKeys,
                                        ZIMError *errorInfo) {
                               if ([self isSucc:errorInfo reject:reject]) {
                                 resolve(@{@"communityID" : communityID, @"errorKeys" : errorKeys});
                               }
                             }];
}

RCT_EXPORT_METHOD(setCommunityNotificationStatus
                  : (NSString *)handle status
                  : (int)status communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim setCommunityNotificationStatus:(ZIMCommunityMessageNotificationStatus)status
                          communityID:communityID
                             callback:^(NSString *communityID, ZIMError *errorInfo) {
                               if ([self isSucc:errorInfo reject:reject]) {
                                 resolve(@{@"communityID" : communityID});
                               }
                             }];
}

RCT_EXPORT_METHOD(queryCommunityInfo
                  : (NSString *)handle communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryCommunityInfoByCommunityID:communityID
                              callback:^(ZIMCommunityFullInfo *communityInfo, ZIMError *errorInfo) {
                                if ([self isSucc:errorInfo reject:reject]) {
                                  resolve(@{
                                    @"communityInfo" : [self mZIMCommunityFullInfo:communityInfo]
                                  });
                                }
                              }];
}

RCT_EXPORT_METHOD(updateCommunityMemberRole
                  : (NSString *)handle memberRole
                  : (int)memberRole forUserID
                  : (NSString *)forUserID communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateCommunityMemberRole:memberRole
                       forUserID:forUserID
                     communityID:communityID
                        callback:^(NSString *communityID, NSString *forUserID, int memberRole,
                                   ZIMError *errorInfo) {
                          if ([self isSucc:errorInfo reject:reject]) {
                            resolve(@{
                              @"communityID" : communityID,
                              @"forUserID" : forUserID,
                              @"memberRole" : @(memberRole)
                            });
                          }
                        }];
}

RCT_EXPORT_METHOD(transferCommunityOwner
                  : (NSString *)handle toUserID
                  : (NSString *)toUserID communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim transferCommunityOwnerToUserID:toUserID
                          communityID:communityID
                             callback:^(NSString *communityID, NSString *toUserID,
                                        ZIMError *errorInfo) {
                               if ([self isSucc:errorInfo reject:reject]) {
                                 resolve(@{
                                   @"communityID" : communityID,
                                   @"toUserID" : toUserID,
                                 });
                               }
                             }];
}

RCT_EXPORT_METHOD(queryCommunityMemberList
                  : (NSString *)handle communityID
                  : (NSString *)communityID count
                  : (int)count config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCommunityMemberListQueryConfig *configObj = [[ZIMCommunityMemberListQueryConfig alloc] init];
  if (config && ![config isEqual:[NSNull null]]) {
    configObj.nextFlag = (unsigned int)[RCTConvert uint64_t:config[@"nextFlag"]];
    configObj.channelID = [RCTConvert NSString:config[@"channelID"]];
    configObj.isMutedMember = [RCTConvert BOOL:config[@"isMutedMember"]];
  }

  [zim queryCommunityMemberListByCommunityID:communityID
                                       count:(unsigned int)count
                                      config:configObj
                                    callback:^(NSString *communityID,
                                               NSArray<ZIMCommunityMemberInfo *> *memberList,
                                               long long nextFlag, ZIMError *errorInfo) {
                                      if ([self isSucc:errorInfo reject:reject]) {
                                        resolve(@{
                                          @"communityID" : communityID,
                                          @"nextFlag" : @(nextFlag),
                                          @"memberList" :
                                              [self mZIMCommunityMemberInfoList:memberList]
                                        });
                                      }
                                    }];
}

RCT_EXPORT_METHOD(queryCommunityMembers
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryCommunityMembersByUserIDs:userIDs
                          communityID:communityID
                             callback:^(NSString *communityID,
                                        NSArray<ZIMCommunityMemberInfo *> *memberList,
                                        NSArray<ZIMErrorUserInfo *> *errorUserList,
                                        ZIMError *errorInfo) {
                               if ([self isSucc:errorInfo reject:reject]) {
                                 resolve(@{
                                   @"communityID" : communityID,
                                   @"memberList" : [self mZIMCommunityMemberInfoList:memberList],
                                   @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                                 });
                               }
                             }];
}

RCT_EXPORT_METHOD(createCommunityChannel
                  : (NSString *)handle channelInfo
                  : (NSDictionary *)channelInfo config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCommunityChannelInfo *channelInfoObj = [[ZIMCommunityChannelInfo alloc] init];
  channelInfoObj.communityID = [RCTConvert NSString:channelInfo[@"communityID"]];
  channelInfoObj.channelType =
      (ZIMCommunityChannelType)[RCTConvert int:channelInfo[@"channelType"]];
  channelInfoObj.channelID = [RCTConvert NSString:channelInfo[@"channelID"]];
  channelInfoObj.channelName = [RCTConvert NSString:channelInfo[@"channelName"]];
  channelInfoObj.channelAvatarUrl = [RCTConvert NSString:channelInfo[@"channelAvatarUrl"]];

  ZIMCommunityChannelCreateConfig *configObj = [[ZIMCommunityChannelCreateConfig alloc] init];
  if (config && ![config isEqual:[NSNull null]]) {
    configObj.channelNotice = [RCTConvert NSString:config[@"channelNotice"]];

    NSDictionary *arrtsDict = [RCTConvert NSDictionary:config[@"channelAttributes"]];
    if (arrtsDict && ![arrtsDict isEqual:[NSNull null]]) {
      configObj.channelAttributes = arrtsDict;
    }
  }

  [zim createCommunityChannelWithChannelInfo:channelInfoObj
                                      config:configObj
                                    callback:^(ZIMCommunityChannelFullInfo *channelInfo,
                                               ZIMError *errorInfo) {
                                      if ([self isSucc:errorInfo reject:reject]) {
                                        resolve(@{
                                          @"channelInfo" :
                                              [self mZIMCommunityChannelFullInfo:channelInfo]
                                        });
                                      }
                                    }];
}

RCT_EXPORT_METHOD(dismissCommunityChannel
                  : (NSString *)handle channelID
                  : (NSString *)channelID communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim dismissCommunityChannelByChannelID:channelID
                              communityID:communityID
                                 callback:^(NSString *communityID, NSString *channelID,
                                            ZIMError *errorInfo) {
                                   if ([self isSucc:errorInfo reject:reject]) {
                                     resolve(
                                         @{@"communityID" : communityID, @"channelID" : channelID});
                                   }
                                 }];
}

RCT_EXPORT_METHOD(updateCommunityChannelName
                  : (NSString *)handle channelName
                  : (NSString *)channelName channelID
                  : (NSString *)channelID communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateCommunityChannelName:channelName
                        channelID:channelID
                      communityID:communityID
                         callback:^(NSString *communityID, NSString *channelID,
                                    NSString *channelName, ZIMError *errorInfo) {
                           if ([self isSucc:errorInfo reject:reject]) {
                             resolve(@{
                               @"communityID" : communityID,
                               @"channelID" : channelID,
                               @"channelName" : channelName
                             });
                           }
                         }];
}

RCT_EXPORT_METHOD(updateCommunityChannelAvatarUrl
                  : (NSString *)handle channelAvatarUrl
                  : (NSString *)channelAvatarUrl channelID
                  : (NSString *)channelID communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateCommunityChannelAvatarUrl:channelAvatarUrl
                             channelID:channelID
                           communityID:communityID
                              callback:^(NSString *communityID, NSString *channelID,
                                         NSString *channelAvatarUrl, ZIMError *errorInfo) {
                                if ([self isSucc:errorInfo reject:reject]) {
                                  resolve(@{
                                    @"communityID" : communityID,
                                    @"channelID" : channelID,
                                    @"channelAvatarUrl" : channelAvatarUrl
                                  });
                                }
                              }];
}

RCT_EXPORT_METHOD(updateCommunityChannelNotice
                  : (NSString *)handle channelNotice
                  : (NSString *)channelNotice channelID
                  : (NSString *)channelID communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateCommunityChannelNotice:channelNotice
                          channelID:channelID
                        communityID:communityID
                           callback:^(NSString *communityID, NSString *channelID,
                                      NSString *channelNotice, ZIMError *errorInfo) {
                             if ([self isSucc:errorInfo reject:reject]) {
                               resolve(@{
                                 @"communityID" : communityID,
                                 @"channelID" : channelID,
                                 @"channelNotice" : channelNotice
                               });
                             }
                           }];
}

RCT_EXPORT_METHOD(setCommunityChannelAttributes
                  : (NSString *)handle channelAttributes
                  : (NSDictionary *)channelAttributes channelID
                  : (NSString *)channelID communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim setCommunityChannelAttributes:channelAttributes
                           channelID:channelID
                         communityID:communityID
                            callback:^(NSString *communityID, NSString *channelID,
                                       NSArray<NSString *> *errorKeys, ZIMError *errorInfo) {
                              if ([self isSucc:errorInfo reject:reject]) {
                                resolve(@{
                                  @"communityID" : communityID,
                                  @"channelID" : channelID,
                                  @"errorKeys" : errorKeys
                                });
                              }
                            }];
}

RCT_EXPORT_METHOD(deleteCommunityChannelAttributes
                  : (NSString *)handle keys
                  : (NSArray<NSString *> *)keys channelID
                  : (NSString *)channelID communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim deleteCommunityChannelAttributesByKeys:keys
                                    channelID:channelID
                                  communityID:communityID
                                     callback:^(NSString *communityID, NSString *channelID,
                                                NSArray<NSString *> *errorKeys,
                                                ZIMError *errorInfo) {
                                       if ([self isSucc:errorInfo reject:reject]) {
                                         resolve(@{
                                           @"communityID" : communityID,
                                           @"channelID" : channelID,
                                           @"errorKeys" : errorKeys
                                         });
                                       }
                                     }];
}

RCT_EXPORT_METHOD(queryCommunityChannelsInfo
                  : (NSString *)handle channelIDs
                  : (NSArray<NSString *> *)channelIDs communityID
                  : (NSString *)communityID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim
      queryCommunityChannelsInfoByChannelIDs:channelIDs
                                 communityID:communityID
                                    callback:^(NSString *communityID,
                                               NSArray<ZIMCommunityChannelFullInfo *> *channelInfos,
                                               NSArray<NSString *> *errorChannelIDs,
                                               ZIMError *errorInfo) {
                                      if ([self isSucc:errorInfo reject:reject]) {
                                        resolve(@{
                                          @"communityID" : communityID,
                                          @"errorChannelIDs" : errorChannelIDs,
                                          @"channelInfos" :
                                              [self mZIMCommunityChannelFullInfoList:channelInfos]
                                        });
                                      }
                                    }];
}

RCT_EXPORT_METHOD(queryCommunityChannelList
                  : (NSString *)handle communityID
                  : (NSString *)communityID count
                  : (int)count config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCommunityChannelListQueryConfig *configObj = [[ZIMCommunityChannelListQueryConfig alloc] init];
  if (config && ![config isEqual:[NSNull null]]) {
    configObj.nextFlag = (unsigned int)[RCTConvert uint64_t:config[@"nextFlag"]];
  }

  [zim queryCommunityChannelListByCommunityID:communityID
                                        count:(unsigned int)count
                                       config:configObj
                                     callback:^(NSString *communityID,
                                                NSArray<ZIMCommunityChannel *> *channelList,
                                                long long nextFlag, ZIMError *errorInfo) {
                                       if ([self isSucc:errorInfo reject:reject]) {
                                         resolve(@{
                                           @"communityID" : communityID,
                                           @"nextFlag" : @(nextFlag),
                                           @"channelList" :
                                               [self mZIMCommunityChannelList:channelList],
                                         });
                                       }
                                     }];
}

RCT_EXPORT_METHOD(muteCommunityChannels
                  : (NSString *)handle isMute
                  : (BOOL)isMute channelIDs
                  : (NSArray<NSString *> *)channelIDs communityID
                  : (NSString *)communityID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCommunityChannelMuteConfig *configObj = [[ZIMCommunityChannelMuteConfig alloc] init];
  if (config && ![config isEqual:[NSNull null]]) {
    configObj.mode = (ZIMCommunityChannelMuteMode)[RCTConvert int:config[@"mode"]];
    configObj.duration = [RCTConvert int:config[@"duration"]];
    NSMutableArray *rolesArray = config[@"roles"];
    if (rolesArray != nil) {
      configObj.roles = rolesArray;
    }
  }

  [zim muteCommunityChannels:isMute
                  channelIDs:channelIDs
                 communityID:communityID
                      config:configObj
                    callback:^(NSString *communityID, BOOL isMute,
                               NSArray<NSString *> *errorChannelIDs, ZIMError *errorInfo) {
                      if ([self isSucc:errorInfo reject:reject]) {
                        resolve(@{
                          @"communityID" : communityID,
                          @"isMute" : [NSNumber numberWithBool:isMute],
                          @"errorChannelIDs" : errorChannelIDs
                        });
                      }
                    }];
}

RCT_EXPORT_METHOD(muteCommunityMembers
                  : (NSString *)handle isMute
                  : (BOOL)isMute userIDs
                  : (NSArray<NSString *> *)userIDs communityID
                  : (NSString *)communityID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCommunityMemberMuteConfig *configObj = [[ZIMCommunityMemberMuteConfig alloc] init];
  if (config && ![config isEqual:[NSNull null]]) {
    configObj.duration = (unsigned int)[RCTConvert uint64_t:config[@"duration"]];
    configObj.channelID = [RCTConvert NSString:config[@"channelID"]];
  }

  [zim muteCommunityMembers:isMute
                    userIDs:userIDs
                communityID:communityID
                     config:configObj
                   callback:^(NSString *communityID, NSString *channelID, BOOL isMute,
                              NSArray<ZIMErrorUserInfo *> *errorUserList, ZIMError *errorInfo) {
                     if ([self isSucc:errorInfo reject:reject]) {
                       resolve(@{
                         @"communityID" : communityID,
                         @"isMute" : [NSNumber numberWithBool:isMute],
                         @"channelID" : channelID,
                         @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                       });
                     }
                   }];
}

RCT_EXPORT_METHOD(callInvite
                  : (NSString *)handle invitees
                  : (NSArray<NSString *> *)invitees config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCallInviteConfig *configObj = [[ZIMCallInviteConfig alloc] init];
  configObj.mode = (ZIMCallInvitationMode)[RCTConvert int:config[@"mode"]];
  configObj.timeout = [RCTConvert int:config[@"timeout"]];
  configObj.extendedData = [RCTConvert NSString:config[@"extendedData"]];
  configObj.enableNotReceivedCheck = [RCTConvert BOOL:config[@"enableNotReceivedCheck"]];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim callInviteWithInvitees:invitees
                       config:configObj
                     callback:^(NSString *callID, ZIMCallInvitationSentInfo *info,
                                ZIMError *errorInfo) {
                       if ([self isSucc:errorInfo reject:reject]) {
                         resolve(@{
                           @"callID" : callID,
                           @"timeout" : @(info.timeout),
                           @"errorUserList" : [self mZIMErrorUserInfoList:info.errorUserList],
                         });
                       }
                     }];
}

RCT_EXPORT_METHOD(callCancel
                  : (NSString *)handle invitees
                  : (NSArray<NSString *> *)invitees callID
                  : (NSString *)callID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCallCancelConfig *configObj = [[ZIMCallCancelConfig alloc] init];
  configObj.extendedData = [RCTConvert NSString:config[@"extendedData"]];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim callCancelWithInvitees:invitees
                       callID:callID
                       config:configObj
                     callback:^(NSString *callID, NSArray<NSString *> *errorInvitees,
                                ZIMError *errorInfo) {
                       if ([self isSucc:errorInfo reject:reject]) {
                         resolve(@{@"callID" : callID, @"errorInvitees" : errorInvitees});
                       }
                     }];
}

RCT_EXPORT_METHOD(callAccept
                  : (NSString *)handle callID
                  : (NSString *)callID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCallAcceptConfig *configObj = [[ZIMCallAcceptConfig alloc] init];
  configObj.extendedData = [RCTConvert NSString:config[@"extendedData"]];

  [zim callAcceptWithCallID:callID
                     config:configObj
                   callback:^(NSString *callID, ZIMError *errorInfo) {
                     if ([self isSucc:errorInfo reject:reject]) {
                       resolve(@{@"callID" : callID});
                     }
                   }];
}

RCT_EXPORT_METHOD(callReject
                  : (NSString *)handle callID
                  : (NSString *)callID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCallRejectConfig *configObj = [[ZIMCallRejectConfig alloc] init];
  configObj.extendedData = [RCTConvert NSString:config[@"extendedData"]];

  [zim callRejectWithCallID:callID
                     config:configObj
                   callback:^(NSString *callID, ZIMError *errorInfo) {
                     if ([self isSucc:errorInfo reject:reject]) {
                       resolve(@{@"callID" : callID});
                     }
                   }];
}

RCT_EXPORT_METHOD(callQuit
                  : (NSString *)handle callID
                  : (NSString *)callID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCallQuitConfig *configObj = [[ZIMCallQuitConfig alloc] init];
  configObj.extendedData = [RCTConvert NSString:config[@"extendedData"]];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim callQuit:callID
         config:configObj
       callback:^(NSString *callID, ZIMCallQuitSentInfo *info, ZIMError *errorInfo) {
         if ([self isSucc:errorInfo reject:reject]) {
           resolve(@{
             @"callID" : callID,
             @"createTime" : @(info.createTime),
             @"acceptTime" : @(info.acceptTime),
             @"quitTime" : @(info.quitTime)
           });
         }
       }];
}

RCT_EXPORT_METHOD(callEnd
                  : (NSString *)handle callID
                  : (NSString *)callID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCallEndConfig *configObj = [[ZIMCallEndConfig alloc] init];
  configObj.extendedData = [RCTConvert NSString:config[@"extendedData"]];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim callEnd:callID
        config:configObj
      callback:^(NSString *callID, ZIMCallEndedSentInfo *info, ZIMError *errorInfo) {
        if ([self isSucc:errorInfo reject:reject]) {
          resolve(@{
            @"callID" : callID,
            @"createTime" : @(info.createTime),
            @"acceptTime" : @(info.acceptTime),
            @"endTime" : @(info.endTime)
          });
        }
      }];
}

RCT_EXPORT_METHOD(callJoin
                  : (NSString *)handle callID
                  : (NSString *)callID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCallJoinConfig *configObj = [[ZIMCallJoinConfig alloc] init];
  configObj.extendedData = [RCTConvert NSString:config[@"extendedData"]];

  [zim callJoin:callID
         config:configObj
       callback:^(NSString *callID, ZIMCallJoinSentInfo *info, ZIMError *errorInfo) {
         if ([self isSucc:errorInfo reject:reject]) {
           resolve(@{
             @"callID" : callID,
             @"createTime" : @(info.createTime),
             @"joinTime" : @(info.joinTime),
             @"extendedData" : info.extendedData,
             @"callUserList" : [self mZIMCallUserList:info.callUserList]
           });
         }
       }];
}

RCT_EXPORT_METHOD(callingInvite
                  : (NSString *)handle invitees
                  : (NSArray<NSString *> *)invitees callID
                  : (NSString *)callID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCallingInviteConfig *configObj = [[ZIMCallingInviteConfig alloc] init];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim callingInviteWithInvitees:invitees
                          callID:callID
                          config:configObj
                        callback:^(NSString *callID, ZIMCallingInvitationSentInfo *info,
                                   ZIMError *errorInfo) {
                          if ([self isSucc:errorInfo reject:reject]) {
                            resolve(@{
                              @"callID" : callID,
                              @"errorUserList" : [self mZIMErrorUserInfoList:info.errorUserList]
                            });
                          }
                        }];
}

RCT_EXPORT_METHOD(queryCallInvitationList
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMCallInvitationQueryConfig *configObj = [[ZIMCallInvitationQueryConfig alloc] init];
  configObj.count = (unsigned int)[RCTConvert uint64_t:config[@"count"]];
  configObj.nextFlag = [RCTConvert uint64_t:config[@"nextFlag"]];

  [zim queryCallInvitationListWithConfig:configObj
                                callback:^(NSArray<ZIMCallInfo *> *callList, long long nextFlag,
                                           ZIMError *errorInfo) {
                                  if ([self isSucc:errorInfo reject:reject]) {
                                    NSMutableArray<NSDictionary *> *callListArray =
                                        [NSMutableArray array];
                                    for (ZIMCallInfo *info in callList) {
                                      [callListArray addObject:@{
                                        @"mode" : @(info.mode),
                                        @"callID" : info.callID,
                                        @"caller" : info.caller,
                                        @"state" : @(info.state),
                                        @"extendedData" : info.extendedData,
                                        @"createTime" : @(info.createTime),
                                        @"endTime" : @(info.endTime),
                                        @"callUserList" : [self mZIMCallUserList:info.callUserList]
                                      }];
                                    }
                                    resolve(
                                        @{@"nextFlag" : @(nextFlag),
                                          @"callList" : callListArray});
                                  }
                                }];
}

RCT_EXPORT_METHOD(searchConversations
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMConversationSearchConfig *configObj = [[ZIMConversationSearchConfig alloc] init];
  configObj.mode = (ZIMConversationSearchMode)[RCTConvert int:config[@"mode"]];
  configObj.totalConversationCount =
      (unsigned int)[RCTConvert uint64_t:config[@"totalConversationCount"]];
  configObj.conversationMessageCount =
      (unsigned int)[RCTConvert uint64_t:config[@"conversationMessageCount"]];
  configObj.nextFlag = (unsigned int)[RCTConvert uint64_t:config[@"nextFlag"]];
  configObj.startTime = [RCTConvert uint64_t:config[@"startTime"]];
  configObj.endTime = [RCTConvert uint64_t:config[@"endTime"]];

  NSMutableArray *keywordsArray = config[@"keywords"];
  if (keywordsArray != nil) {
    configObj.keywords = keywordsArray;
  }
  NSMutableArray *senderUserIDsArray = config[@"senderUserIDs"];
  if (senderUserIDsArray != nil) {
    configObj.senderUserIDs = senderUserIDsArray;
  }
  NSMutableArray *messageTypesArray = config[@"messageTypes"];
  if (messageTypesArray != nil) {
    configObj.messageTypes = messageTypesArray;
  }
  NSMutableArray *subMessageTypesArray = config[@"subMessageTypes"];
  if (subMessageTypesArray != nil) {
    configObj.subMessageTypes = subMessageTypesArray;
  }

  [zim
      searchLocalConversationsWithConfig:configObj
                                callback:^(NSArray<ZIMConversationSearchInfo *>
                                               *conversationSearchInfoList,
                                           unsigned int nextFlag, ZIMError *errorInfo) {
                                  if ([self isSucc:errorInfo reject:reject]) {
                                    NSMutableArray<NSDictionary *> *listArray =
                                        [NSMutableArray array];
                                    for (ZIMConversationSearchInfo
                                             *info in conversationSearchInfoList) {
                                      [listArray addObject:@{
                                        @"conversationID" : info.conversationID,
                                        @"conversationType" : @(info.conversationType),
                                        @"totalMessageCount" : @(info.totalMessageCount),
                                        @"conversation" : [self mZIMConversation:info.conversation],
                                        @"messageList" : [self mZIMMessageList:info.messageList]
                                      }];
                                    }
                                    resolve(@{
                                      @"nextFlag" : @(nextFlag),
                                      @"conversationSearchInfoList" : listArray
                                    });
                                  }
                                }];
}

RCT_EXPORT_METHOD(searchGlobalMessages
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessageSearchConfig *configObj = [[ZIMMessageSearchConfig alloc] init];
  configObj.count = (unsigned int)[RCTConvert uint64_t:config[@"count"]];
  configObj.order = (ZIMMessageOrder)[RCTConvert int:config[@"order"]];
  configObj.startTime = [RCTConvert uint64_t:config[@"startTime"]];
  configObj.endTime = [RCTConvert uint64_t:config[@"endTime"]];
  configObj.nextMessage = [self oZIMMessage:config[@"nextMessage"]];

  NSMutableArray *keywordsArray = config[@"keywords"];
  if (keywordsArray != nil) {
    configObj.keywords = keywordsArray;
  }
  NSMutableArray *senderUserIDsArray = config[@"senderUserIDs"];
  if (senderUserIDsArray != nil) {
    configObj.senderUserIDs = senderUserIDsArray;
  }
  NSMutableArray *messageTypesArray = config[@"messageTypes"];
  if (messageTypesArray != nil) {
    configObj.messageTypes = messageTypesArray;
  }
  NSMutableArray *subMessageTypesArray = config[@"subMessageTypes"];
  if (subMessageTypesArray != nil) {
    configObj.subMessageTypes = subMessageTypesArray;
  }

  [zim searchGlobalLocalMessagesWithConfig:configObj
                                  callback:^(NSArray<ZIMMessage *> *messageList,
                                             ZIMMessage *_Nullable nextMessage,
                                             ZIMError *errorInfo) {
                                    if ([self isSucc:errorInfo reject:reject]) {
                                      NSDictionary *msgDict = [self mZIMMessage:nextMessage];
                                      resolve(@{
                                        @"nextMessage" : msgDict == nil ? [NSNull null] : msgDict,
                                        @"messageList" : [self mZIMMessageList:messageList]
                                      });
                                    }
                                  }];
}

RCT_EXPORT_METHOD(searchMessages
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMMessageSearchConfig *configObj = [[ZIMMessageSearchConfig alloc] init];
  configObj.count = (unsigned int)[RCTConvert uint64_t:config[@"count"]];
  configObj.order = (ZIMMessageOrder)[RCTConvert int:config[@"order"]];
  configObj.startTime = [RCTConvert uint64_t:config[@"startTime"]];
  configObj.endTime = [RCTConvert uint64_t:config[@"endTime"]];
  configObj.nextMessage = [self oZIMMessage:config[@"nextMessage"]];

  NSMutableArray *keywordsArray = config[@"keywords"];
  if (keywordsArray != nil) {
    configObj.keywords = keywordsArray;
  }
  NSMutableArray *senderUserIDsArray = config[@"senderUserIDs"];
  if (senderUserIDsArray != nil) {
    configObj.senderUserIDs = senderUserIDsArray;
  }
  NSMutableArray *messageTypesArray = config[@"messageTypes"];
  if (messageTypesArray != nil) {
    configObj.messageTypes = messageTypesArray;
  }
  NSMutableArray *subMessageTypesArray = config[@"subMessageTypes"];
  if (subMessageTypesArray != nil) {
    configObj.subMessageTypes = subMessageTypesArray;
  }

  [zim
      searchLocalMessagesByConversationID:conversationID
                         conversationType:conversationType
                                   config:configObj
                                 callback:^(
                                     NSString *conversationID, ZIMConversationType conversationType,
                                     NSArray<ZIMMessage *> *messageList,
                                     ZIMMessage *_Nullable nextMessage, ZIMError *errorInfo) {
                                   if ([self isSucc:errorInfo reject:reject]) {
                                     NSDictionary *msgDict = [self mZIMMessage:nextMessage];
                                     resolve(@{
                                       @"conversationID" : conversationID,
                                       @"conversationType" : @(conversationType),
                                       @"nextMessage" : msgDict == nil ? [NSNull null] : msgDict,
                                       @"messageList" : [self mZIMMessageList:messageList]
                                     });
                                   }
                                 }];
}

RCT_EXPORT_METHOD(searchGroups
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupSearchConfig *configObj = [[ZIMGroupSearchConfig alloc] init];
  configObj.count = (unsigned int)[RCTConvert uint64_t:config[@"count"]];
  configObj.nextFlag = (unsigned int)[RCTConvert uint64_t:config[@"nextFlag"]];
  configObj.isAlsoMatchGroupMemberUserName =
      [RCTConvert BOOL:config[@"isAlsoMatchGroupMemberUserName"]];
  configObj.isAlsoMatchGroupMemberNickname =
      [RCTConvert BOOL:config[@"isAlsoMatchGroupMemberNickname"]];

  NSMutableArray *keywordsArray = config[@"keywords"];
  if (keywordsArray != nil) {
    configObj.keywords = keywordsArray;
  }

  [zim searchLocalGroupsWithConfig:configObj
                          callback:^(NSArray<ZIMGroupSearchInfo *> *groupSearchInfoList,
                                     unsigned int nextFlag, ZIMError *errorInfo) {
                            if ([self isSucc:errorInfo reject:reject]) {
                              NSMutableArray<NSDictionary *> *listArray = [NSMutableArray array];
                              for (ZIMGroupSearchInfo *info in groupSearchInfoList) {
                                [listArray addObject:@{
                                  @"groupInfo" : @{
                                    @"groupID" : info.groupInfo.groupID,
                                    @"groupName" : info.groupInfo.groupName,
                                    @"groupAvatarUrl" : info.groupInfo.groupAvatarUrl
                                  },
                                  @"userList" : [self mZIMGroupMemberInfoList:info.userList]
                                }];
                              }
                              resolve(
                                  @{@"nextFlag" : @(nextFlag),
                                    @"groupSearchInfoList" : listArray});
                            }
                          }];
}

RCT_EXPORT_METHOD(searchGroupMembers
                  : (NSString *)handle groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMGroupMemberSearchConfig *configObj = [[ZIMGroupMemberSearchConfig alloc] init];
  configObj.count = (unsigned int)[RCTConvert uint64_t:config[@"count"]];
  configObj.nextFlag = (unsigned int)[RCTConvert uint64_t:config[@"nextFlag"]];
  configObj.isAlsoMatchGroupMemberNickname =
      [RCTConvert BOOL:config[@"isAlsoMatchGroupMemberNickname"]];

  NSMutableArray *keywordsArray = config[@"keywords"];
  if (keywordsArray != nil) {
    configObj.keywords = keywordsArray;
  }

  [zim
      searchLocalGroupMembersByGroupID:groupID
                                config:configObj
                              callback:^(NSString *groupID, NSArray<ZIMGroupMemberInfo *> *userList,
                                         unsigned int nextFlag, ZIMError *errorInfo) {
                                if ([self isSucc:errorInfo reject:reject]) {
                                  resolve(@{
                                    @"nextFlag" : @(nextFlag),
                                    @"groupID" : groupID,
                                    @"userList" : [self mZIMGroupMemberInfoList:userList],
                                  });
                                }
                              }];
}

RCT_EXPORT_METHOD(searchFriends
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMFriendSearchConfig *configObj = [[ZIMFriendSearchConfig alloc] init];
  configObj.count = (unsigned int)[RCTConvert uint64_t:config[@"count"]];
  configObj.nextFlag = (unsigned int)[RCTConvert uint64_t:config[@"nextFlag"]];
  configObj.isAlsoMatchFriendAlias = [RCTConvert BOOL:config[@"isAlsoMatchFriendAlias"]];

  NSMutableArray *keywordsArray = config[@"keywords"];
  if (keywordsArray != nil) {
    configObj.keywords = keywordsArray;
  }

  [zim searchLocalFriendsWithConfig:configObj
                           callback:^(NSArray<ZIMFriendInfo *> *userList, unsigned int nextFlag,
                                      ZIMError *errorInfo) {
                             if ([self isSucc:errorInfo reject:reject]) {
                               resolve(@{
                                 @"nextFlag" : @(nextFlag),
                                 @"friendInfos" : [self mZIMFriendInfoList:userList],
                               });
                             }
                           }];
}

RCT_EXPORT_METHOD(addFriend
                  : (NSString *)handle userID
                  : (NSString *)userID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMFriendAddConfig *configObj = [[ZIMFriendAddConfig alloc] init];
  configObj.wording = [RCTConvert NSString:config[@"wording"]];
  configObj.friendAlias = [RCTConvert NSString:config[@"friendAlias"]];
  configObj.friendAttributes = [RCTConvert NSDictionary:config[@"friendAttributes"]];

  [zim addFriendByUserID:userID
                  config:configObj
                callback:^(ZIMFriendInfo *friendInfo, ZIMError *errorInfo) {
                  if ([self isSucc:errorInfo reject:reject]) {
                    resolve(@{@"friendInfo" : [self mZIMFriendInfo:friendInfo]});
                  }
                }];
}

RCT_EXPORT_METHOD(sendFriendApplication
                  : (NSString *)handle userID
                  : (NSString *)userID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMFriendApplicationSendConfig *configObj = [[ZIMFriendApplicationSendConfig alloc] init];
  configObj.pushConfig = [self mZIMPushConfig:config];
  configObj.wording = [RCTConvert NSString:config[@"wording"]];
  configObj.friendAlias = [RCTConvert NSString:config[@"friendAlias"]];
  configObj.friendAttributes = [RCTConvert NSDictionary:config[@"friendAttributes"]];

  [zim sendFriendApplicationToUserID:userID
                              config:configObj
                            callback:^(ZIMFriendApplicationInfo *info, ZIMError *errorInfo) {
                              if ([self isSucc:errorInfo reject:reject]) {
                                resolve(@{
                                  @"applicationInfo" : @{
                                    @"type" : @(info.type),
                                    @"state" : @(info.state),
                                    @"wording" : info.wording,
                                    @"createTime" : @(info.createTime),
                                    @"updateTime" : @(info.updateTime),
                                    @"applyUser" : [self mZIMUserInfo:info.applyUser],
                                  }
                                });
                              }
                            }];
}

RCT_EXPORT_METHOD(deleteFriends
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMFriendDeleteConfig *configObj = [[ZIMFriendDeleteConfig alloc] init];
  configObj.type = (ZIMFriendDeleteType)[RCTConvert int:config[@"type"]];

  [zim deleteFriendsByUserIDs:userIDs
                       config:configObj
                     callback:^(NSArray<ZIMErrorUserInfo *> *errorUsers, ZIMError *errorInfo) {
                       if ([self isSucc:errorInfo reject:reject]) {
                         resolve(@{@"errorUserList" : [self mZIMErrorUserInfoList:errorUsers]});
                       }
                     }];
}

RCT_EXPORT_METHOD(checkFriendsRelation
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMFriendRelationCheckConfig *configObj = [[ZIMFriendRelationCheckConfig alloc] init];
  configObj.type = (ZIMFriendRelationCheckType)[RCTConvert int:config[@"type"]];

  [zim checkFriendsRelationByUserIDs:userIDs
                              config:configObj
                            callback:^(NSArray<ZIMFriendRelationInfo *> *infos,
                                       NSArray<ZIMErrorUserInfo *> *errorUsers,
                                       ZIMError *errorInfo) {
                              if ([self isSucc:errorInfo reject:reject]) {
                                NSMutableArray *resultArray = [NSMutableArray array];
                                for (ZIMFriendRelationInfo *info in infos) {
                                  [resultArray addObject:@{
                                    @"userID" : info.userID,
                                    @"type" : @(info.type)
                                  }];
                                }

                                resolve(@{
                                  @"relationInfos" : resultArray,
                                  @"errorUserList" : [self mZIMErrorUserInfoList:errorUsers]
                                });
                              }
                            }];
}

RCT_EXPORT_METHOD(updateFriendAlias
                  : (NSString *)handle friendAlias
                  : (NSString *)friendAlias userID
                  : (NSString *)userID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateFriendAlias:friendAlias
                  userID:userID
                callback:^(ZIMFriendInfo *friendInfo, ZIMError *errorInfo) {
                  if ([self isSucc:errorInfo reject:reject]) {
                    resolve(@{@"friendInfo" : [self mZIMFriendInfo:friendInfo]});
                  }
                }];
}

RCT_EXPORT_METHOD(updateFriendAttributes
                  : (NSString *)handle friendAttributes
                  : (NSDictionary *)friendAttributes userID
                  : (NSString *)userID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim updateFriendAttributes:friendAttributes
                       userID:userID
                     callback:^(ZIMFriendInfo *friendInfo, ZIMError *errorInfo) {
                       if ([self isSucc:errorInfo reject:reject]) {
                         resolve(@{@"friendInfo" : [self mZIMFriendInfo:friendInfo]});
                       }
                     }];
}

RCT_EXPORT_METHOD(acceptFriendApplication
                  : (NSString *)handle userID
                  : (NSString *)userID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMFriendApplicationAcceptConfig *configObj = [[ZIMFriendApplicationAcceptConfig alloc] init];
  configObj.pushConfig = [self mZIMPushConfig:config];
  configObj.friendAlias = [RCTConvert NSString:config[@"friendAlias"]];
  configObj.friendAttributes = [RCTConvert NSDictionary:config[@"friendAttributes"]];

  [zim acceptFriendApplicationFromUserID:userID
                                  config:configObj
                                callback:^(ZIMFriendInfo *friendInfo, ZIMError *errorInfo) {
                                  if ([self isSucc:errorInfo reject:reject]) {
                                    resolve(@{@"friendInfo" : [self mZIMFriendInfo:friendInfo]});
                                  }
                                }];
}

RCT_EXPORT_METHOD(rejectFriendApplication
                  : (NSString *)handle userID
                  : (NSString *)userID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMFriendApplicationRejectConfig *configObj = [[ZIMFriendApplicationRejectConfig alloc] init];
  configObj.pushConfig = [self mZIMPushConfig:config];

  [zim rejectFriendApplicationFromUserID:userID
                                  config:configObj
                                callback:^(ZIMUserInfo *userInfo, ZIMError *errorInfo) {
                                  if ([self isSucc:errorInfo reject:reject]) {
                                    resolve(@{@"userInfo" : [self mZIMUserInfo:userInfo]});
                                  }
                                }];
}

RCT_EXPORT_METHOD(queryFriendsInfo
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim queryFriendsInfoByUserIDs:userIDs
                        callback:^(NSArray<ZIMFriendInfo *> *infos,
                                   NSArray<ZIMErrorUserInfo *> *errorUsers, ZIMError *errorInfo) {
                          if ([self isSucc:errorInfo reject:reject]) {
                            resolve(@{
                              @"friendInfos" : [self mZIMFriendInfoList:infos],
                              @"errorUserList" : [self mZIMErrorUserInfoList:errorUsers]
                            });
                          }
                        }];
}

RCT_EXPORT_METHOD(queryFriendList
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMFriendListQueryConfig *configObj = [[ZIMFriendListQueryConfig alloc] init];
  configObj.count = [RCTConvert int:config[@"count"]];
  configObj.nextFlag = [RCTConvert int:config[@"nextFlag"]];

  [zim queryFriendListWithConfig:configObj
                        callback:^(NSArray<ZIMFriendInfo *> *infos, unsigned int nextFlag,
                                   ZIMError *errorInfo) {
                          if ([self isSucc:errorInfo reject:reject]) {
                            resolve(@{
                              @"nextFlag" : @(nextFlag),
                              @"friendList" : [self mZIMFriendInfoList:infos]
                            });
                          }
                        }];
}

RCT_EXPORT_METHOD(queryFriendApplicationList
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMFriendApplicationListQueryConfig *configObj =
      [[ZIMFriendApplicationListQueryConfig alloc] init];
  configObj.count = [RCTConvert int:config[@"count"]];
  configObj.nextFlag = [RCTConvert int:config[@"nextFlag"]];

  [zim queryFriendApplicationListWithConfig:configObj
                                   callback:^(NSArray<ZIMFriendApplicationInfo *> *infos,
                                              unsigned int nextFlag, ZIMError *errorInfo) {
                                     if ([self isSucc:errorInfo reject:reject]) {
                                       resolve(@{
                                         @"nextFlag" : @(nextFlag),
                                         @"applicationList" : [self mZIMFriendApplicationList:infos]
                                       });
                                     }
                                   }];
}

RCT_EXPORT_METHOD(addUsersToBlacklist
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim addUsersToBlacklistWithUserIDs:userIDs
                             callback:^(NSArray<ZIMErrorUserInfo *> *errorUsers,
                                        ZIMError *errorInfo) {
                               if ([self isSucc:errorInfo reject:reject]) {
                                 resolve(
                                     @{@"errorUserList" : [self mZIMErrorUserInfoList:errorUsers]});
                               }
                             }];
}

RCT_EXPORT_METHOD(removeUsersFromBlacklist
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim removeUsersFromBlacklistWithUserIDs:userIDs
                                  callback:^(NSArray<ZIMErrorUserInfo *> *errorUsers,
                                             ZIMError *errorInfo) {
                                    if ([self isSucc:errorInfo reject:reject]) {
                                      resolve(@{
                                        @"errorUserList" : [self mZIMErrorUserInfoList:errorUsers]
                                      });
                                    }
                                  }];
}

RCT_EXPORT_METHOD(checkUserIsInBlacklist
                  : (NSString *)handle userID
                  : (NSString *)userID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  [zim
      checkUserIsInBlacklistByUserID:userID
                            callback:^(BOOL isUserInBlacklist, ZIMError *errorInfo) {
                              if ([self isSucc:errorInfo reject:reject]) {
                                resolve(@{
                                  @"isUserInBlacklist" : [NSNumber numberWithBool:isUserInBlacklist]
                                });
                              }
                            }];
}

RCT_EXPORT_METHOD(queryBlacklist
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self getZIMEngine:handle reject:reject];
  if (!zim) {
    return;
  }

  ZIMBlacklistQueryConfig *configObj = [[ZIMBlacklistQueryConfig alloc] init];
  configObj.count = [RCTConvert int:config[@"count"]];
  configObj.nextFlag = [RCTConvert int:config[@"nextFlag"]];

  [zim queryBlacklistWithConfig:configObj
                       callback:^(NSArray<ZIMUserInfo *> *infos, unsigned int nextFlag,
                                  ZIMError *errorInfo) {
                         if ([self isSucc:errorInfo reject:reject]) {
                           resolve(@{
                             @"nextFlag" : @(nextFlag),
                             @"blacklist" : [self mZIMUserInfoList:infos]
                           });
                         }
                       }];
}

#pragma mark Callback

- (void)zim:(ZIM *)zim errorInfo:(ZIMError *)errorInfo {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"error")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"code" : @(errorInfo.code), @"message" : errorInfo.message}
                       }];
  }
}

- (void)zim:(ZIM *)zim
    connectionStateChanged:(ZIMConnectionState)state
                     event:(ZIMConnectionEvent)event
              extendedData:(NSDictionary *)extendedData {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    NSString *dataStr = @"{}";
    if (extendedData) {
      NSData *jsonData = [NSJSONSerialization dataWithJSONObject:extendedData options:0 error:0];
      dataStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    }

    [self sendEventWithName:RN_EVENT(@"connectionStateChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" :
                             @{@"state" : @(state), @"event" : @(event), @"extendedData" : dataStr}
                       }];
  }
}

- (void)zim:(ZIM *)zim experimentalEventReceived:(NSString *)content {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"experimentalEventReceived")
                       body:@{@"handle" : handle, @"data" : @{@"data" : content}}];
  }
}

- (void)zim:(ZIM *)zim tokenWillExpire:(unsigned int)second {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"tokenWillExpire")
                       body:@{@"handle" : handle, @"data" : @{@"second" : @(second)}}];
  }
}

- (void)zim:(ZIM *)zim userInfoUpdated:(ZIMUserFullInfo *)info {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"userInfoUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"info" : [self mZIMUserFullInfo:info]}
                       }];
  }
}

- (void)zim:(ZIM *)zim userStatusUpdated:(NSArray<ZIMUserStatus *> *)userStatusList {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"userStatusUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"userStatusList" : [self mZIMUserStatusList:userStatusList]}
                       }];
  }
}

- (void)zim:(ZIM *)zim userRuleUpdated:(ZIMUserRule *)info {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"userRuleUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"userRule" : [self mZIMUserRule:info]}
                       }];
  }
}

- (void)zim:(ZIM *)zim conversationsAllDeleted:(ZIMConversationsAllDeletedInfo *)info {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"conversationsAllDeleted")
                       body:@{@"handle" : handle, @"data" : @{@"count" : @(info.count)}}];
  }
}

- (void)zim:(ZIM *)zim
    conversationChanged:(NSArray<ZIMConversationChangeInfo *> *)conversationChangeInfoList {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *convInfoArray = [NSMutableArray array];
    for (ZIMConversationChangeInfo *changeInfo in conversationChangeInfoList) {
      NSDictionary *convDict = [self mZIMConversation:changeInfo.conversation];
      [convInfoArray addObject:@{
        @"event" : @(changeInfo.event),
        @"action" : @(changeInfo.action),
        @"conversation" : convDict
      }];
    }

    [self sendEventWithName:RN_EVENT(@"conversationChanged")
                       body:@{@"handle" : handle, @"data" : @{@"infoList" : convInfoArray}}];
  }
}

- (void)zim:(ZIM *)zim conversationSyncStateChanged:(ZIMConversationSyncState)state {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"conversationSyncStateChanged")
                       body:@{@"handle" : handle, @"data" : @{@"state" : @(state)}}];
  }
}

- (void)zim:(ZIM *)zim
    conversationTotalUnreadMessageCountUpdated:(unsigned int)totalUnreadMessageCount {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"conversationTotalUnreadMessageCountUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"totalUnreadMessageCount" : @(totalUnreadMessageCount)}
                       }];
  }
}

- (void)zim:(ZIM *)zim
    peerMessageReceived:(nonnull NSArray<ZIMMessage *> *)messageList
                   info:(nonnull ZIMMessageReceivedInfo *)info
             fromUserID:(nonnull NSString *)fromUserID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"peerMessageReceived")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"info" : @{
                             @"isOfflineMessage" : @(info.isOfflineMessage),
                             @"conversationID" : info.conversationID,
                             @"conversationType" : @(info.conversationType)
                           },
                           @"fromConversationID" : fromUserID,
                           @"messageList" : [self mZIMMessageList:messageList],
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    roomMessageReceived:(nonnull NSArray<ZIMMessage *> *)messageList
                   info:(nonnull ZIMMessageReceivedInfo *)info
             fromRoomID:(nonnull NSString *)fromRoomID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"roomMessageReceived")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"info" : @{
                             @"isOfflineMessage" : @(info.isOfflineMessage),
                             @"conversationID" : info.conversationID,
                             @"conversationType" : @(info.conversationType)
                           },
                           @"fromConversationID" : fromRoomID,
                           @"messageList" : [self mZIMMessageList:messageList],
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    groupMessageReceived:(nonnull NSArray<ZIMMessage *> *)messageList
                    info:(nonnull ZIMMessageReceivedInfo *)info
             fromGroupID:(nonnull NSString *)fromGroupID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"groupMessageReceived")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"fromConversationID" : fromGroupID,
                           @"info" : @{
                             @"isOfflineMessage" : @(info.isOfflineMessage),
                             @"conversationID" : info.conversationID,
                             @"conversationType" : @(info.conversationType)
                           },
                           @"messageList" : [self mZIMMessageList:messageList],
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim messageReceivedWithResult:(nonnull ZIMMessageReceivedEventResult *)result {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"messageReceived")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"receivedInfo" : @{
                             @"isOfflineMessage" : @(result.receivedInfo.isOfflineMessage),
                             @"conversationID" : result.receivedInfo.conversationID,
                             @"conversationType" : @(result.receivedInfo.conversationType)
                           },
                           @"messageList" : [self mZIMMessageList:result.messageList],
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim broadcastMessageReceived:(ZIMMessage *)message {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"broadcastMessageReceived")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"message" : [self mZIMMessage:message]}
                       }];
  }
}

- (void)zim:(ZIM *)zim conversationMessageReceiptChanged:(NSArray<ZIMMessageReceiptInfo *> *)infos {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    [self sendEventWithName:RN_EVENT(@"conversationMessageReceiptChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"infos" : [self mZIMMessageReceiptInfoList:infos]}
                       }];
  }
}

- (void)zim:(ZIM *)zim messageReceiptChanged:(NSArray<ZIMMessageReceiptInfo *> *)infos {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    [self sendEventWithName:RN_EVENT(@"messageReceiptChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"infos" : [self mZIMMessageReceiptInfoList:infos]}
                       }];
  }
}

- (void)zim:(ZIM *)zim messageRevokeReceived:(NSArray<ZIMRevokeMessage *> *)messageList {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"messageRevokeReceived")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"messageList" : [self mZIMMessageList:messageList]}
                       }];
  }
}

- (void)zim:(ZIM *)zim
    messageReactionsChangedWithResult:(nonnull ZIMMessageReactionsChangedEventResult *)result {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *infosArray = [NSMutableArray array];
    for (ZIMMessageReactionChangeInfo *info in result.changeInfoList) {
      NSMutableArray *userArray = [NSMutableArray array];
      for (ZIMMessageReactionUserChangeInfo *user in info.userChangeInfoList) {
        [userArray addObject:@{
          @"userID" : user.userID,
          @"reactionType" : user.reactionType,
          @"action" : @(user.action),
          @"sumCount" : @(user.sumCount),
        }];
      }

      [infosArray addObject:@{
        @"userChangeInfoList" : userArray,
        @"message" : [self mZIMMessage:info.message]
      }];
    }

    [self sendEventWithName:RN_EVENT(@"messageReactionsChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"reactions" : [self mZIMMessageReactionList:result.reactions],
                           @"changeInfoList" : infosArray
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    messageRepliedCountChanged:(NSArray<ZIMMessageRootRepliedCountInfo *> *)infos {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *infosArray = [NSMutableArray array];
    for (ZIMMessageRootRepliedCountInfo *info in infos) {
      [infosArray addObject:@{
        @"conversationID" : info.conversationID,
        @"conversationType" : @(info.conversationType),
        @"count" : @(info.count),
        @"messageID" : [[NSNumber numberWithLongLong:info.messageID] stringValue]
      }];
    }

    [self sendEventWithName:RN_EVENT(@"messageRepliedCountChanged")
                       body:@{@"handle" : handle, @"data" : @{@"infos" : infosArray}}];
  }
}

- (void)zim:(ZIM *)zim messageRepliedInfoChanged:(NSArray<ZIMMessage *> *)messageList {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"messageRepliedInfoChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"messageList" : [self mZIMMessageList:messageList]}
                       }];
  }
}

- (void)zim:(ZIM *)zim messagePinStatusChanged:(NSArray<ZIMMessagePinStatusChangeInfo *> *)infos {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *infosArray = [NSMutableArray array];
    for (ZIMMessagePinStatusChangeInfo *info in infos) {
      [infosArray addObject:@{
        @"pinStatus" : @(info.pinStatus),
        @"message" : [self mZIMMessage:info.message]
      }];
    }

    [self sendEventWithName:RN_EVENT(@"messagePinStatusChanged")
                       body:@{@"handle" : handle, @"data" : @{@"infos" : infosArray}}];
  }
}

- (void)zim:(ZIM *)zim messageEdited:(NSArray<ZIMMessage *> *)messageList {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"messageEdited")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"messageList" : [self mZIMMessageList:messageList]}
                       }];
  }
}

- (void)zim:(ZIM *)zim messageDeleted:(ZIMMessageDeletedInfo *)deletedInfo {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"messageDeleted")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"conversationID" : deletedInfo.conversationID,
                           @"conversationType" : @(deletedInfo.conversationType),
                           @"messageDeleteType" : @(deletedInfo.messageDeleteType),
                           @"messageList" : [self mZIMMessageList:deletedInfo.messageList]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    messageSentStatusChanged:
        (NSArray<ZIMMessageSentStatusChangeInfo *> *)messageSentStatusChangeInfoList {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *infosArray = [NSMutableArray array];
    for (ZIMMessageSentStatusChangeInfo *info in messageSentStatusChangeInfoList) {
      NSDictionary *msgDict = [self mZIMMessage:info.message];
      [infosArray
          addObject:@{@"status" : @(info.status), @"reason" : info.reason, @"message" : msgDict}];
    }

    [self sendEventWithName:RN_EVENT(@"messageSentStatusChanged")
                       body:@{@"handle" : handle, @"data" : @{@"infos" : infosArray}}];
  }
}

- (void)zim:(ZIM *)zim
    roomMemberJoined:(NSArray<ZIMUserInfo *> *)memberList
              roomID:(NSString *)roomID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    [self
        sendEventWithName:RN_EVENT(@"roomMemberJoined")
                     body:@{
                       @"handle" : handle,
                       @"data" :
                           @{@"roomID" : roomID, @"memberList" : [self mZIMUserInfoList:memberList]}
                     }];
  }
}

- (void)zim:(ZIM *)zim
    roomMemberLeft:(NSArray<ZIMUserInfo *> *)memberList
            roomID:(NSString *)roomID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    [self
        sendEventWithName:RN_EVENT(@"roomMemberLeft")
                     body:@{
                       @"handle" : handle,
                       @"data" :
                           @{@"roomID" : roomID, @"memberList" : [self mZIMUserInfoList:memberList]}
                     }];
  }
}

- (void)zim:(ZIM *)zim
    roomStateChanged:(ZIMRoomState)state
               event:(ZIMRoomEvent)event
        extendedData:(NSDictionary *)extendedData
              roomID:(NSString *)roomID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    NSString *dataStr = @"{}";
    if (extendedData) {
      NSData *jsonData = [NSJSONSerialization dataWithJSONObject:extendedData options:0 error:0];
      dataStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    }
    [self sendEventWithName:RN_EVENT(@"roomStateChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"roomID" : roomID,
                           @"state" : @(state),
                           @"event" : @(event),
                           @"extendedData" : dataStr
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    roomAttributesUpdated:(ZIMRoomAttributesUpdateInfo *)updateInfo
                   roomID:(NSString *)roomID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *updateInfoArray = [NSMutableArray array];
    [updateInfoArray addObject:@{
      @"action" : @(updateInfo.action),
      @"roomAttributes" : updateInfo.roomAttributes
    }];

    [self sendEventWithName:RN_EVENT(@"roomAttributesUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"roomID" : roomID, @"infos" : updateInfoArray}
                       }];
  }
}

- (void)zim:(ZIM *)zim
    roomAttributesBatchUpdated:(NSArray<ZIMRoomAttributesUpdateInfo *> *)updateInfo
                        roomID:(NSString *)roomID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *updateInfoArray = [NSMutableArray array];
    for (ZIMRoomAttributesUpdateInfo *info in updateInfo) {
      [updateInfoArray
          addObject:@{@"action" : @(info.action), @"roomAttributes" : info.roomAttributes}];
    }

    [self sendEventWithName:RN_EVENT(@"roomAttributesBatchUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"roomID" : roomID, @"infos" : updateInfoArray}
                       }];
  }
}

- (void)zim:(ZIM *)zim
    roomMemberAttributesUpdated:(NSArray<ZIMRoomMemberAttributesUpdateInfo *> *)infos
                   operatedInfo:(ZIMRoomOperatedInfo *)operatedInfo
                         roomID:(NSString *)roomID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableDictionary *operatedInfoMap = [[NSMutableDictionary alloc] init];
    operatedInfoMap[@"userID"] = operatedInfo.userID;

    NSMutableArray *infosArray = [NSMutableArray array];
    for (ZIMRoomMemberAttributesUpdateInfo *info in infos) {
      [infosArray addObject:@{
        @"attributesInfo" : @{
          @"userID" : info.attributesInfo.userID,
          @"attributes" : info.attributesInfo.attributes
        }
      }];
    }

    [self sendEventWithName:RN_EVENT(@"roomMemberAttributesUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"roomID" : roomID,
                           @"operatedInfo" : operatedInfoMap,
                           @"infos" : infosArray
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    groupStateChanged:(ZIMGroupState)state
                event:(ZIMGroupEvent)event
         operatedInfo:(ZIMGroupOperatedInfo *)operatedInfo
            groupInfo:(ZIMGroupFullInfo *)groupInfo {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"groupStateChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"state" : @(state),
                           @"event" : @(event),
                           @"operatedInfo" : [self mZIMGroupOperatedInfo:operatedInfo],
                           @"groupInfo" : [self mZIMGroupFullInfo:groupInfo]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    groupNameUpdated:(NSString *)groupName
        operatedInfo:(ZIMGroupOperatedInfo *)operatedInfo
             groupID:(NSString *)groupID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"groupNameUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"groupID" : groupID,
                           @"groupName" : groupName,
                           @"operatedInfo" : [self mZIMGroupOperatedInfo:operatedInfo]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    groupAvatarUrlUpdated:(NSString *)groupAvatarUrl
             operatedInfo:(ZIMGroupOperatedInfo *)operatedInfo
                  groupID:(NSString *)groupID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"groupAvatarUrlUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"groupID" : groupID,
                           @"groupAvatarUrl" : groupAvatarUrl,
                           @"operatedInfo" : [self mZIMGroupOperatedInfo:operatedInfo]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    groupNoticeUpdated:(NSString *)groupNotice
          operatedInfo:(ZIMGroupOperatedInfo *)operatedInfo
               groupID:(NSString *)groupID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"groupNoticeUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"groupID" : groupID,
                           @"groupNotice" : groupNotice,
                           @"operatedInfo" : [self mZIMGroupOperatedInfo:operatedInfo]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    groupAttributesUpdated:(NSArray<ZIMGroupAttributesUpdateInfo *> *)updateInfo
              operatedInfo:(ZIMGroupOperatedInfo *)operatedInfo
                   groupID:(NSString *)groupID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *updateInfoArray = [NSMutableArray array];
    for (ZIMGroupAttributesUpdateInfo *info in updateInfo) {
      [updateInfoArray
          addObject:@{@"action" : @(info.action), @"groupAttributes" : info.groupAttributes}];
    }

    [self sendEventWithName:RN_EVENT(@"groupAttributesUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"groupID" : groupID,
                           @"operatedInfo" : [self mZIMGroupOperatedInfo:operatedInfo],
                           @"infoList" : updateInfoArray
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    groupMutedInfoUpdated:(ZIMGroupMuteInfo *)muteInfo
             operatedInfo:(ZIMGroupOperatedInfo *)operatedInfo
                  groupID:(NSString *)groupID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    [self sendEventWithName:RN_EVENT(@"groupMutedInfoUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"groupID" : groupID,
                           @"mutedInfo" : [self mZIMGroupMuteInfo:muteInfo],
                           @"operatedInfo" : [self mZIMGroupOperatedInfo:operatedInfo],
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    groupVerifyInfoUpdated:(ZIMGroupVerifyInfo *)verifyInfo
              operatedInfo:(ZIMGroupOperatedInfo *)operatedInfo
                   groupID:(NSString *)groupID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    [self sendEventWithName:RN_EVENT(@"groupVerifyInfoUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"groupID" : groupID,
                           @"verifyInfo" : [self mZIMGroupVerifyInfo:verifyInfo],
                           @"operatedInfo" : [self mZIMGroupOperatedInfo:operatedInfo],
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    groupApplicationListChanged:(NSArray<ZIMGroupApplicationInfo *> *)infoList
                         action:(ZIMGroupApplicationListChangeAction)action {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"groupApplicationListChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"action" : @(action),
                           @"applicationList" : [self mZIMGroupApplicationList:infoList]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim groupApplicationUpdated:(NSArray<ZIMGroupApplicationInfo *> *)infoList {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"groupApplicationUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"applicationList" : [self mZIMGroupApplicationList:infoList]}
                       }];
  }
}

- (void)zim:(ZIM *)zim
    groupMemberStateChanged:(ZIMGroupMemberState)state
                      event:(ZIMGroupMemberEvent)event
                   userList:(NSArray<ZIMGroupMemberInfo *> *)userList
               operatedInfo:(ZIMGroupOperatedInfo *)operatedInfo
                    groupID:(NSString *)groupID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    [self sendEventWithName:RN_EVENT(@"groupMemberStateChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"groupID" : groupID,
                           @"state" : @(state),
                           @"event" : @(event),
                           @"operatedInfo" : [self mZIMGroupOperatedInfo:operatedInfo],
                           @"userList" : [self mZIMGroupMemberInfoList:userList]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    groupMemberInfoUpdated:(NSArray<ZIMGroupMemberInfo *> *)userList
              operatedInfo:(ZIMGroupOperatedInfo *)operatedInfo
                   groupID:(NSString *)groupID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    [self sendEventWithName:RN_EVENT(@"groupMemberInfoUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"groupID" : groupID,
                           @"operatedInfo" : [self mZIMGroupOperatedInfo:operatedInfo],
                           @"userList" : [self mZIMGroupMemberInfoList:userList]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    groupAliasUpdated:(NSString *)groupAlias
       operatedUserID:(NSString *)operatedUserID
              groupID:(NSString *)groupID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    [self sendEventWithName:RN_EVENT(@"groupAliasUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"groupID" : groupID,
                           @"operatedUserID" : operatedUserID,
                           @"groupAlias" : groupAlias
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim communityListChangedWithResult:(ZIMCommunityListChangedEventResult *)result {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *infosArray = [NSMutableArray array];
    for (ZIMCommunityChangeInfo *info in result.changeInfoList) {
      [infosArray addObject:@{
        @"action" : @(info.action),
        @"community" : [self mZIMCommunity:info.community]
      }];
    }

    [self sendEventWithName:RN_EVENT(@"communityListChanged")
                       body:@{@"handle" : handle, @"data" : @{@"changeInfoList" : infosArray}}];
  }
}

- (void)zim:(ZIM *)zim communityInfoUpdatedWithResult:(ZIMCommunityInfoUpdatedEventResult *)result {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *infosArray = [NSMutableArray array];
    for (ZIMCommunityFullInfoUpdateInfo *info in result.updateInfoList) {
      [infosArray addObject:@{@"communityInfo" : [self mZIMCommunityFullInfo:info.communityInfo]}];
    }

    [self sendEventWithName:RN_EVENT(@"communityInfoUpdated")
                       body:@{@"handle" : handle, @"data" : @{@"updateInfoList" : infosArray}}];
  }
}

- (void)zim:(ZIM *)zim
    communityMemberStateChangedWithResult:(ZIMCommunityMemberStateChangedEventResult *)result {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *infosArray = [NSMutableArray array];
    for (ZIMCommunityMemberStateChangeInfo *info in result.changeInfoList) {
      [infosArray addObject:@{
        @"state" : @(info.state),
        @"action" : @(info.action),
        @"memberInfo" : [self mZIMCommunityMemberSimpleInfo:info.memberInfo]
      }];
    }

    [self sendEventWithName:RN_EVENT(@"communityMemberStateChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" :
                             @{@"communityID" : result.communityID, @"changeInfoList" : infosArray}
                       }];
  }
}

- (void)zim:(ZIM *)zim
    communityMemberInfoUpdatedWithResult:(ZIMCommunityMemberInfoUpdatedEventResult *)result {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *infosArray = [NSMutableArray array];
    for (ZIMCommunityMemberInfoUpdateInfo *info in result.updateInfoList) {
      [infosArray addObject:@{@"memberInfo" : [self mZIMCommunityMemberInfo:info.memberInfo]}];
    }

    [self sendEventWithName:RN_EVENT(@"communityMemberInfoUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" :
                             @{@"communityID" : result.communityID, @"updateInfoList" : infosArray}
                       }];
  }
}

- (void)zim:(ZIM *)zim
    communityChannelListChangedWithResult:(ZIMCommunityChannelListChangedEventResult *)result {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *infosArray = [NSMutableArray array];
    for (ZIMCommunityChannelChangeInfo *info in result.changeInfoList) {
      [infosArray addObject:@{
        @"action" : @(info.action),
        @"channel" : [self mZIMCommunityChannel:info.channel]
      }];
    }

    [self sendEventWithName:RN_EVENT(@"communityChannelListChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" :
                             @{@"communityID" : result.communityID, @"changeInfoList" : infosArray}
                       }];
  }
}

- (void)zim:(ZIM *)zim
    communityChannelInfoUpdatedWithResult:(ZIMCommunityChannelInfoUpdatedEventResult *)result {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];

    NSMutableArray *infosArray = [NSMutableArray array];
    for (ZIMCommunityChannelFullInfoUpdateInfo *info in result.updateInfoList) {
      [infosArray
          addObject:@{@"channelInfo" : [self mZIMCommunityChannelFullInfo:info.channelInfo]}];
    }

    [self sendEventWithName:RN_EVENT(@"communityChannelInfoUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" :
                             @{@"communityID" : result.communityID, @"updateInfoList" : infosArray}
                       }];
  }
}

- (void)zim:(ZIM *)zim
    callInvitationCreated:(ZIMCallInvitationCreatedInfo *)info
                   callID:(NSString *)callID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"callInvitationCreated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"mode" : @(info.mode),
                           @"callID" : callID,
                           @"caller" : info.caller,
                           @"extendedData" : info.extendedData,
                           @"timeout" : @(info.timeout),
                           @"createTime" : @(info.createTime),
                           @"callUserList" : [self mZIMCallUserList:info.callUserList]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    callInvitationReceived:(ZIMCallInvitationReceivedInfo *)info
                    callID:(NSString *)callID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"callInvitationReceived")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"mode" : @(info.mode),
                           @"callID" : callID,
                           @"caller" : info.caller,
                           @"inviter" : info.inviter,
                           @"extendedData" : info.extendedData,
                           @"timeout" : @(info.timeout),
                           @"createTime" : @(info.createTime),
                           @"callUserList" : [self mZIMCallUserList:info.callUserList]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    callInvitationCancelled:(ZIMCallInvitationCancelledInfo *)info
                     callID:(NSString *)callID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"callInvitationCancelled")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"mode" : @(info.mode),
                           @"callID" : callID,
                           @"inviter" : info.inviter,
                           @"extendedData" : info.extendedData
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    callInvitationTimeout:(ZIMCallInvitationTimeoutInfo *)info
                   callID:(NSString *)callID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"callInvitationTimeout")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"mode" : @(info.mode), @"callID" : callID}
                       }];
  }
}

- (void)zim:(ZIM *)zim
    callInvitationEnded:(ZIMCallInvitationEndedInfo *)info
                 callID:(NSString *)callID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"callInvitationEnded")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"mode" : @(info.mode),
                           @"callID" : callID,
                           @"caller" : info.caller,
                           @"operatedUserID" : info.operatedUserID,
                           @"extendedData" : info.extendedData,
                           @"endTime" : @(info.endTime)
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    callUserStateChanged:(ZIMCallUserStateChangeInfo *)info
                  callID:(NSString *)callID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"callUserStateChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"callID" : callID,
                           @"callUserList" : [self mZIMCallUserList:info.callUserList]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim
    blacklistChanged:(NSArray<ZIMUserInfo *> *)userList
              action:(ZIMBlacklistChangeAction)action {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self
        sendEventWithName:RN_EVENT(@"blacklistChanged")
                     body:@{
                       @"handle" : handle,
                       @"data" :
                           @{@"action" : @(action), @"userList" : [self mZIMUserInfoList:userList]}
                     }];
  }
}

- (void)zim:(ZIM *)zim
    friendListChanged:(NSArray<ZIMFriendInfo *> *)infoList
               action:(ZIMFriendListChangeAction)action {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"friendListChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"action" : @(action),
                           @"friendList" : [self mZIMFriendInfoList:infoList]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim friendInfoUpdated:(NSArray<ZIMFriendInfo *> *)infoList {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"friendInfoUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"friendList" : [self mZIMFriendInfoList:infoList]}
                       }];
  }
}

- (void)zim:(ZIM *)zim
    friendApplicationListChanged:(NSArray<ZIMFriendApplicationInfo *> *)infoList
                          action:(ZIMFriendApplicationListChangeAction)action {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"friendApplicationListChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"action" : @(action),
                           @"applicationList" : [self mZIMFriendApplicationList:infoList]
                         }
                       }];
  }
}

- (void)zim:(ZIM *)zim friendApplicationUpdated:(NSArray<ZIMFriendApplicationInfo *> *)infoList {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"friendApplicationUpdated")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"applicationList" : [self mZIMFriendApplicationList:infoList]}
                       }];
  }
}

//==== Util ====//

- (nullable ZIMMessage *)oZIMMessage:(nullable NSDictionary *)dict {
  if (!dict || [dict isEqual:[NSNull null]]) {
    return nil;
  }

  id msg = nil;

  ZIMMessageType type = (ZIMMessageType)[RCTConvert int:dict[@"type"]];
  switch (type) {
    case ZIMMessageTypeUnknown: {
      msg = [[ZIMMessage alloc] init];
      break;
    }
    case ZIMMessageTypeText: {
      msg = [[ZIMTextMessage alloc] init];
      ((ZIMTextMessage *)msg).message = [RCTConvert NSString:dict[@"message"]];
      break;
    }
    case ZIMMessageTypeCommand: {
      msg = [[ZIMCommandMessage alloc] init];

      NSMutableArray *messageMap = dict[@"message"];
      Byte *bytes = malloc(sizeof(Byte) * (messageMap.count));
      for (int i = 0; i < messageMap.count; i++) {
        bytes[i] = [messageMap[i] intValue];
      }

      NSData *data = [NSData dataWithBytes:bytes length:messageMap.count];
      ((ZIMCommandMessage *)msg).message = data;
      free(bytes);
      break;
    }
    case ZIMMessageTypeBarrage: {
      msg = [[ZIMBarrageMessage alloc] init];
      ((ZIMBarrageMessage *)msg).message = [RCTConvert NSString:dict[@"message"]];
      break;
    }
    case ZIMMessageTypeRevoke: {
      msg = [[ZIMRevokeMessage alloc] init];
      break;
    }
    case ZIMMessageTypeTips: {
      msg = [[ZIMTipsMessage alloc] init];
      break;
    }
    case ZIMMessageTypeCustom: {
      msg = [[ZIMCustomMessage alloc] init];
      ((ZIMCustomMessage *)msg).message = [RCTConvert NSString:dict[@"message"]];
      break;
    }
    case ZIMMessageTypeCombine: {
      msg = [[ZIMCombineMessage alloc] init];
      break;
    }
    case ZIMMessageTypeMultiple: {
      msg = [[ZIMMultipleMessage alloc] init];
      break;
    }
    case ZIMMessageTypeFile: {
      msg = [[ZIMFileMessage alloc] init];
      break;
    }
    case ZIMMessageTypeAudio: {
      msg = [[ZIMAudioMessage alloc] init];

      ((ZIMAudioMessage *)msg).audioDuration =
          (unsigned int)[RCTConvert NSUInteger:dict[@"audioDuration"]];
      break;
    }
    case ZIMMessageTypeVideo: {
      msg = [[ZIMVideoMessage alloc] init];
      ((ZIMVideoMessage *)msg).videoDuration =
          (unsigned int)[RCTConvert NSUInteger:dict[@"videoDuration"]];
      ((ZIMVideoMessage *)msg).videoFirstFrameDownloadUrl =
          [RCTConvert NSString:dict[@"videoFirstFrameDownloadUrl"]];
      [((ZIMVideoMessage *)msg) safeSetValue:[RCTConvert NSString:dict[@"videoFirstFrameLocalPath"]]
                                      forKey:@"videoFirstFrameLocalPath"];
      [((ZIMVideoMessage *)msg)
          safeSetValue:@(CGSizeMake([RCTConvert NSUInteger:dict[@"videoFirstFrameWidth"]],
                                    [RCTConvert NSUInteger:dict[@"videoFirstFrameHeight"]]))
                forKey:@"videoFirstFrameSize"];
      break;
    }
    case ZIMMessageTypeImage: {
      msg = [[ZIMImageMessage alloc] init];
      ((ZIMImageMessage *)msg).thumbnailDownloadUrl =
          [RCTConvert NSString:dict[@"thumbnailDownloadUrl"]];
      ((ZIMImageMessage *)msg).largeImageDownloadUrl =
          [RCTConvert NSString:dict[@"largeImageDownloadUrl"]];
      [((ZIMImageMessage *)msg) safeSetValue:[RCTConvert NSString:dict[@"thumbnailLocalPath"]]
                                      forKey:@"thumbnailLocalPath"];
      [((ZIMImageMessage *)msg) safeSetValue:[RCTConvert NSString:dict[@"largeImageLocalPath"]]
                                      forKey:@"largeImageLocalPath"];
      [((ZIMImageMessage *)msg)
          safeSetValue:@(CGSizeMake([RCTConvert NSUInteger:dict[@"originalImageWidth"]],
                                    [RCTConvert NSUInteger:dict[@"originalImageHeight"]]))
                forKey:@"originalImageSize"];
      [((ZIMImageMessage *)msg) safeSetValue:@(CGSizeMake(
                                                 [RCTConvert NSUInteger:dict[@"thumbnailWidth"]],
                                                 [RCTConvert NSUInteger:dict[@"thumbnailHeight"]]))
                                      forKey:@"thumbnailSize"];
      [((ZIMImageMessage *)msg) safeSetValue:@(CGSizeMake(
                                                 [RCTConvert NSUInteger:dict[@"largeImageWidth"]],
                                                 [RCTConvert NSUInteger:dict[@"largeImageHeight"]]))
                                      forKey:@"largeImageSize"];
      break;
    }
    default:
      break;
  }

  NSString *jsMessageID = [RCTConvert NSString:dict[@"messageID"]];
  NSString *jsLocalMessageID = [RCTConvert NSString:dict[@"localMessageID"]];

  [msg safeSetValue:[RCTConvert NSNumber:dict[@"type"]] forKey:@"type"];
  [msg safeSetValue:[NSNumber numberWithLongLong:[jsMessageID longLongValue]] forKey:@"messageID"];
  [msg safeSetValue:[NSNumber numberWithLongLong:[jsLocalMessageID longLongValue]]
             forKey:@"localMessageID"];
  [msg safeSetValue:[RCTConvert NSString:dict[@"senderUserID"]] forKey:@"senderUserID"];
  [msg safeSetValue:[RCTConvert NSString:dict[@"conversationID"]] forKey:@"conversationID"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"direction"]] forKey:@"direction"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"sentStatus"]] forKey:@"sentStatus"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"conversationType"]] forKey:@"conversationType"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"timestamp"]] forKey:@"timestamp"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"messageSeq"]] forKey:@"messageSeq"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"orderKey"]] forKey:@"orderKey"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"isUserInserted"]] forKey:@"isUserInserted"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"isServerMessage"]] forKey:@"isServerMessage"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"isBroadcastMessage"]] forKey:@"isBroadcastMessage"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"isGroupTargetedMessage"]]
             forKey:@"isGroupTargetedMessage"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"isMentionAll"]] forKey:@"isMentionAll"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"receiptStatus"]] forKey:@"receiptStatus"];
  [msg safeSetValue:[RCTConvert NSString:dict[@"extendedData"]] forKey:@"extendedData"];
  [msg safeSetValue:[RCTConvert NSString:dict[@"localExtendedData"]] forKey:@"localExtendedData"];

  NSMutableArray *mentionedUserIDsArray = dict[@"mentionedUserIDs"];
  if (mentionedUserIDsArray != nil) {
    [msg safeSetValue:mentionedUserIDsArray forKey:@"mentionedUserIDs"];
  }

  if ([msg isKindOfClass:[ZIMMediaMessage class]]) {
    [msg safeSetValue:[RCTConvert NSString:dict[@"fileLocalPath"]] forKey:@"fileLocalPath"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"fileDownloadUrl"]] forKey:@"fileDownloadUrl"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"fileUID"]] forKey:@"fileUID"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"fileName"]] forKey:@"fileName"];
    [msg safeSetValue:[RCTConvert NSNumber:dict[@"fileSize"]] forKey:@"fileSize"];
  } else if ([msg isKindOfClass:[ZIMRevokeMessage class]]) {
    [msg safeSetValue:[RCTConvert NSNumber:dict[@"revokeType"]] forKey:@"revokeType"];
    [msg safeSetValue:[RCTConvert NSNumber:dict[@"revokeStatus"]] forKey:@"revokeStatus"];
    [msg safeSetValue:[RCTConvert NSNumber:dict[@"revokeTimestamp"]] forKey:@"revokeTimestamp"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"operatedUserID"]] forKey:@"operatedUserID"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"revokeExtendedData"]]
               forKey:@"revokeExtendedData"];
    [msg safeSetValue:[RCTConvert NSNumber:dict[@"originalMessageType"]]
               forKey:@"originalMessageType"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"originalTextMessageContent"]]
               forKey:@"originalTextMessageContent"];
  } else if ([msg isKindOfClass:[ZIMTipsMessage class]]) {
    [msg safeSetValue:[RCTConvert NSNumber:dict[@"event"]] forKey:@"event"];
  } else if ([msg isKindOfClass:[ZIMCustomMessage class]]) {
    [msg safeSetValue:[RCTConvert NSNumber:dict[@"subType"]] forKey:@"subType"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"searchedContent"]] forKey:@"searchedContent"];
  } else if ([msg isKindOfClass:[ZIMCombineMessage class]]) {
    [msg safeSetValue:[RCTConvert NSString:dict[@"title"]] forKey:@"title"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"summary"]] forKey:@"summary"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"combineID"]] forKey:@"combineID"];

    NSMutableArray *messageDictArray = dict[@"messageList"];
    NSMutableArray<ZIMMessage *> *messageObjArray = [NSMutableArray array];
    for (NSDictionary *msgdict in messageDictArray) {
      ZIMMessage *message = [self oZIMMessage:msgdict];
      if (message) {
        [messageObjArray addObject:message];
      }
    }
    [msg safeSetValue:messageObjArray forKey:@"messageList"];
  } else if ([msg isKindOfClass:[ZIMMultipleMessage class]]) {
    NSMutableArray *messageDictArray = dict[@"messageInfoList"];
    NSMutableArray<ZIMMessageLiteInfo *> *messageObjArray = [NSMutableArray array];
    for (NSDictionary *msgdict in messageDictArray) {
      ZIMMessageLiteInfo *message = [self oZIMMessageLiteInfo:msgdict];
      if (message) {
        [messageObjArray addObject:message];
      }
    }
    [msg safeSetValue:messageObjArray forKey:@"messageInfoList"];

    NSMutableArray *failedIndexArray = dict[@"failedIndexList"];
    if (failedIndexArray != nil) {
      [msg safeSetValue:failedIndexArray forKey:@"failedIndexList"];
    }
  }

  return msg;
}

- (NSDictionary *)mZIMMessage:(ZIMMessage *)message {
  if (!message) {
    return nil;
  }

  NSMutableDictionary *messageDic = [NSMutableDictionary dictionary];
  NSNumber *longlongMessageID = [NSNumber numberWithLongLong:message.messageID];
  NSNumber *longlongLocalMessageID = [NSNumber numberWithLongLong:message.localMessageID];
  [messageDic safeSetObject:[NSNumber numberWithInt:(int)message.type] forKey:@"type"];
  [messageDic safeSetObject:[longlongMessageID stringValue] forKey:@"messageID"];
  [messageDic safeSetObject:[longlongLocalMessageID stringValue] forKey:@"localMessageID"];

  [messageDic safeSetObject:message.senderUserID forKey:@"senderUserID"];
  [messageDic safeSetObject:message.conversationID forKey:@"conversationID"];
  [messageDic safeSetObject:[NSNumber numberWithInt:(int)message.direction] forKey:@"direction"];
  [messageDic safeSetObject:[NSNumber numberWithInt:(int)message.sentStatus] forKey:@"sentStatus"];

  [messageDic safeSetObject:[NSNumber numberWithInt:(int)message.conversationType]
                     forKey:@"conversationType"];

  [messageDic safeSetObject:[NSNumber numberWithUnsignedLongLong:message.timestamp]
                     forKey:@"timestamp"];

  [messageDic safeSetObject:[NSNumber numberWithLongLong:message.messageSeq] forKey:@"messageSeq"];
  [messageDic safeSetObject:[NSNumber numberWithLongLong:message.orderKey] forKey:@"orderKey"];

  [messageDic safeSetObject:[NSNumber numberWithBool:message.isUserInserted]
                     forKey:@"isUserInserted"];
  [messageDic safeSetObject:[NSNumber numberWithInt:(int)message.receiptStatus]
                     forKey:@"receiptStatus"];
  [messageDic safeSetObject:message.extendedData forKey:@"extendedData"];
  [messageDic safeSetObject:message.localExtendedData forKey:@"localExtendedData"];
  [messageDic safeSetObject:[NSNumber numberWithBool:message.isServerMessage]
                     forKey:@"isServerMessage"];
  [messageDic safeSetObject:[NSNumber numberWithBool:message.isBroadcastMessage]
                     forKey:@"isBroadcastMessage"];
  [messageDic safeSetObject:[NSNumber numberWithBool:message.isGroupTargetedMessage]
                     forKey:@"isGroupTargetedMessage"];
  [messageDic safeSetObject:[NSNumber numberWithBool:message.isMentionAll] forKey:@"isMentionAll"];
  [messageDic safeSetObject:message.mentionedUserIDs forKey:@"mentionedUserIDs"];
  [messageDic safeSetObject:[NSNumber numberWithLongLong:message.rootRepliedCount]
                     forKey:@"rootRepliedCount"];
  [messageDic safeSetObject:[self mZIMMessageRepliedInfo:message.repliedInfo]
                     forKey:@"repliedInfo"];
  [messageDic safeSetObject:[self mZIMMessageReactionList:message.reactions] forKey:@"reactions"];
  [messageDic safeSetObject:message.editorUserID forKey:@"editorUserID"];
  [messageDic safeSetObject:[NSNumber numberWithUnsignedLongLong:message.editedTime]
                     forKey:@"editedTime"];
  [messageDic safeSetObject:message.pinnedUserID forKey:@"pinnedUserID"];
  [messageDic safeSetObject:[NSNumber numberWithUnsignedLongLong:message.pinnedTime]
                     forKey:@"pinnedTime"];
  [messageDic safeSetObject:[NSNumber numberWithUnsignedLongLong:message.destructTime]
                     forKey:@"destructTime"];

  if ([message isKindOfClass:[ZIMMediaMessage class]]) {
    ZIMMediaMessage *mediaMsg = (ZIMMediaMessage *)message;
    [messageDic safeSetObject:mediaMsg.fileLocalPath forKey:@"fileLocalPath"];
    [messageDic safeSetObject:mediaMsg.fileDownloadUrl forKey:@"fileDownloadUrl"];
    [messageDic safeSetObject:mediaMsg.fileUID forKey:@"fileUID"];
    [messageDic safeSetObject:mediaMsg.fileName forKey:@"fileName"];
    [messageDic safeSetObject:[NSNumber numberWithLongLong:mediaMsg.fileSize] forKey:@"fileSize"];
  }

  switch (message.type) {
    case ZIMMessageTypeUnknown: {
      break;
    }
    case ZIMMessageTypeText: {
      ZIMTextMessage *txtMsg = (ZIMTextMessage *)message;
      [messageDic safeSetObject:txtMsg.message forKey:@"message"];
      break;
    }
    case ZIMMessageTypeCommand: {
      ZIMCommandMessage *cmdMsg = (ZIMCommandMessage *)message;
      Byte *bytes = (Byte *)[cmdMsg.message bytes];
      NSMutableDictionary *dataMap = [NSMutableDictionary dictionary];
      for (int i = 0; i < [cmdMsg.message length]; i++) {
        [dataMap setObject:@(bytes[i]) forKey:[NSString stringWithFormat:@"%d", i]];
      }
      [messageDic safeSetObject:dataMap forKey:@"message"];
      break;
    }
    case ZIMMessageTypeBarrage: {
      ZIMBarrageMessage *brgMsg = (ZIMBarrageMessage *)message;
      [messageDic safeSetObject:brgMsg.message forKey:@"message"];
      break;
    }
    case ZIMMessageTypeRevoke: {
      ZIMRevokeMessage *revokeMsg = (ZIMRevokeMessage *)message;
      [messageDic safeSetObject:[NSNumber numberWithInt:(int)revokeMsg.revokeType]
                         forKey:@"revokeType"];
      [messageDic safeSetObject:[NSNumber numberWithInt:(int)revokeMsg.revokeStatus]
                         forKey:@"revokeStatus"];
      [messageDic safeSetObject:[NSNumber numberWithUnsignedLongLong:revokeMsg.revokeTimestamp]
                         forKey:@"revokeTimestamp"];
      [messageDic safeSetObject:revokeMsg.operatedUserID forKey:@"operatedUserID"];
      [messageDic safeSetObject:revokeMsg.revokeExtendedData forKey:@"revokeExtendedData"];
      [messageDic safeSetObject:[NSNumber numberWithInt:(int)revokeMsg.originalMessageType]
                         forKey:@"originalMessageType"];
      [messageDic safeSetObject:revokeMsg.originalTextMessageContent
                         forKey:@"originalTextMessageContent"];
      break;
    }
    case ZIMMessageTypeTips: {
      ZIMTipsMessage *tipsMsg = (ZIMTipsMessage *)message;
      [messageDic safeSetObject:[NSNumber numberWithInt:(int)tipsMsg.event] forKey:@"event"];
      [messageDic safeSetObject:[self mZIMUserOrGroupMember:tipsMsg.operatedUser]
                         forKey:@"operatedUser"];
      [messageDic safeSetObject:[self mZIMUserOrGroupMemberList:tipsMsg.targetUserList]
                         forKey:@"targetUserList"];
      [messageDic safeSetObject:[self mZIMTipsMessageChangeInfo:tipsMsg.changeInfo]
                         forKey:@"changeInfo"];
      break;
    }
    case ZIMMessageTypeCustom: {
      ZIMCustomMessage *customMsg = (ZIMCustomMessage *)message;
      [messageDic safeSetObject:customMsg.message forKey:@"message"];
      [messageDic safeSetObject:[NSNumber numberWithUnsignedInt:customMsg.subType]
                         forKey:@"subType"];
      [messageDic safeSetObject:customMsg.searchedContent forKey:@"searchedContent"];
      break;
    }
    case ZIMMessageTypeCombine: {
      ZIMCombineMessage *combineMsg = (ZIMCombineMessage *)message;
      [messageDic safeSetObject:combineMsg.title forKey:@"title"];
      [messageDic safeSetObject:combineMsg.summary forKey:@"summary"];
      [messageDic safeSetObject:combineMsg.combineID forKey:@"combineID"];
      [messageDic safeSetObject:[self mZIMMessageList:combineMsg.messageList]
                         forKey:@"messageList"];
      break;
    }
    case ZIMMessageTypeFile: {
      break;
    }
    case ZIMMessageTypeImage: {
      ZIMImageMessage *imgMsg = (ZIMImageMessage *)message;
      [messageDic safeSetObject:imgMsg.thumbnailDownloadUrl forKey:@"thumbnailDownloadUrl"];
      [messageDic safeSetObject:imgMsg.thumbnailLocalPath forKey:@"thumbnailLocalPath"];
      [messageDic safeSetObject:imgMsg.largeImageDownloadUrl forKey:@"largeImageDownloadUrl"];
      [messageDic safeSetObject:imgMsg.largeImageLocalPath forKey:@"largeImageLocalPath"];
      [messageDic safeSetObject:@(imgMsg.originalImageSize.width) forKey:@"originalImageWidth"];
      [messageDic safeSetObject:@(imgMsg.originalImageSize.height) forKey:@"originalImageHeight"];
      [messageDic safeSetObject:@(imgMsg.thumbnailSize.width) forKey:@"thumbnailWidth"];
      [messageDic safeSetObject:@(imgMsg.thumbnailSize.height) forKey:@"thumbnailHeight"];
      [messageDic safeSetObject:@(imgMsg.largeImageSize.width) forKey:@"largeImageWidth"];
      [messageDic safeSetObject:@(imgMsg.largeImageSize.height) forKey:@"largeImageHeight"];
      break;
    }
    case ZIMMessageTypeAudio: {
      ZIMAudioMessage *audioMsg = (ZIMAudioMessage *)message;
      [messageDic safeSetObject:[NSNumber numberWithUnsignedInt:audioMsg.audioDuration]
                         forKey:@"audioDuration"];
      break;
    }
    case ZIMMessageTypeVideo: {
      ZIMVideoMessage *videoMsg = (ZIMVideoMessage *)message;
      [messageDic safeSetObject:[NSNumber numberWithUnsignedInt:videoMsg.videoDuration]
                         forKey:@"videoDuration"];
      [messageDic safeSetObject:videoMsg.videoFirstFrameDownloadUrl
                         forKey:@"videoFirstFrameDownloadUrl"];
      [messageDic safeSetObject:videoMsg.videoFirstFrameLocalPath
                         forKey:@"videoFirstFrameLocalPath"];
      [messageDic safeSetObject:@(videoMsg.videoFirstFrameSize.width)
                         forKey:@"videoFirstFrameWidth"];
      [messageDic safeSetObject:@(videoMsg.videoFirstFrameSize.height)
                         forKey:@"videoFirstFrameHeight"];
      break;
    }
    case ZIMMessageTypeMultiple: {
      ZIMMultipleMessage *multipleMsg = (ZIMMultipleMessage *)message;
      NSMutableArray *messageInfoArray = [NSMutableArray array];
      for (ZIMMessageLiteInfo *message in multipleMsg.messageInfoList) {
        NSDictionary *msgDict = [self mZIMMessageLiteInfo:message];
        [messageInfoArray addObject:msgDict];
      }
      [messageDic safeSetObject:messageInfoArray forKey:@"messageInfoList"];
      [messageDic safeSetObject:multipleMsg.failedIndexList forKey:@"failedIndexList"];
      break;
    }
    default:
      break;
  }
  return messageDic;
}

- (NSDictionary *)mZIMMessageLiteInfo:(ZIMMessageLiteInfo *)message {
  if (!message) {
    return nil;
  }

  NSMutableDictionary *messageDic = [NSMutableDictionary dictionary];
  [messageDic safeSetObject:[NSNumber numberWithInt:(int)message.type] forKey:@"type"];

  if ([message isKindOfClass:[ZIMMediaMessageLiteInfo class]]) {
    ZIMMediaMessageLiteInfo *mediaMsg = (ZIMMediaMessageLiteInfo *)message;
    [messageDic safeSetObject:mediaMsg.fileLocalPath forKey:@"fileLocalPath"];
    [messageDic safeSetObject:mediaMsg.fileDownloadUrl forKey:@"fileDownloadUrl"];
    [messageDic safeSetObject:mediaMsg.fileName forKey:@"fileName"];
    [messageDic safeSetObject:[NSNumber numberWithLongLong:mediaMsg.fileSize] forKey:@"fileSize"];
  }

  switch (message.type) {
    case ZIMMessageTypeText: {
      ZIMTextMessageLiteInfo *txtMsg = (ZIMTextMessageLiteInfo *)message;
      [messageDic safeSetObject:txtMsg.message forKey:@"message"];
      break;
    }
    case ZIMMessageTypeCustom: {
      ZIMCustomMessageLiteInfo *customMsg = (ZIMCustomMessageLiteInfo *)message;
      [messageDic safeSetObject:customMsg.message forKey:@"message"];
      [messageDic safeSetObject:[NSNumber numberWithUnsignedInt:customMsg.subType]
                         forKey:@"subType"];
      [messageDic safeSetObject:customMsg.searchedContent forKey:@"searchedContent"];
      break;
    }
    case ZIMMessageTypeCombine: {
      ZIMCombineMessageLiteInfo *combineMsg = (ZIMCombineMessageLiteInfo *)message;
      [messageDic safeSetObject:combineMsg.title forKey:@"title"];
      [messageDic safeSetObject:combineMsg.summary forKey:@"summary"];
      break;
    }
    case ZIMMessageTypeImage: {
      ZIMImageMessageLiteInfo *imgMsg = (ZIMImageMessageLiteInfo *)message;
      [messageDic safeSetObject:imgMsg.thumbnailDownloadUrl forKey:@"thumbnailDownloadUrl"];
      [messageDic safeSetObject:imgMsg.thumbnailLocalPath forKey:@"thumbnailLocalPath"];
      [messageDic safeSetObject:imgMsg.largeImageDownloadUrl forKey:@"largeImageDownloadUrl"];
      [messageDic safeSetObject:imgMsg.largeImageLocalPath forKey:@"largeImageLocalPath"];
      [messageDic safeSetObject:@(imgMsg.originalImageSize.width) forKey:@"originalImageWidth"];
      [messageDic safeSetObject:@(imgMsg.originalImageSize.height) forKey:@"originalImageHeight"];
      [messageDic safeSetObject:@(imgMsg.thumbnailSize.width) forKey:@"thumbnailWidth"];
      [messageDic safeSetObject:@(imgMsg.thumbnailSize.height) forKey:@"thumbnailHeight"];
      [messageDic safeSetObject:@(imgMsg.largeImageSize.width) forKey:@"largeImageWidth"];
      [messageDic safeSetObject:@(imgMsg.largeImageSize.height) forKey:@"largeImageHeight"];
      break;
    }
    case ZIMMessageTypeAudio: {
      ZIMAudioMessageLiteInfo *audioMsg = (ZIMAudioMessageLiteInfo *)message;
      [messageDic safeSetObject:[NSNumber numberWithUnsignedInt:audioMsg.audioDuration]
                         forKey:@"audioDuration"];
      break;
    }
    case ZIMMessageTypeVideo: {
      ZIMVideoMessageLiteInfo *videoMsg = (ZIMVideoMessageLiteInfo *)message;
      [messageDic safeSetObject:[NSNumber numberWithUnsignedInt:videoMsg.videoDuration]
                         forKey:@"videoDuration"];
      [messageDic safeSetObject:videoMsg.videoFirstFrameDownloadUrl
                         forKey:@"videoFirstFrameDownloadUrl"];
      [messageDic safeSetObject:videoMsg.videoFirstFrameLocalPath
                         forKey:@"videoFirstFrameLocalPath"];
      [messageDic safeSetObject:@(videoMsg.videoFirstFrameSize.width)
                         forKey:@"videoFirstFrameWidth"];
      [messageDic safeSetObject:@(videoMsg.videoFirstFrameSize.height)
                         forKey:@"videoFirstFrameHeight"];
      break;
    }
    case ZIMMessageTypeMultiple: {
      ZIMMultipleMessageLiteInfo *multipleMsg = (ZIMMultipleMessageLiteInfo *)message;
      NSMutableArray *messageInfoArray = [NSMutableArray array];
      for (ZIMMessageLiteInfo *msgInfo in multipleMsg.messageInfoList) {
        NSDictionary *msgInfoDict = [self mZIMMessageLiteInfo:msgInfo];
        [messageInfoArray addObject:msgInfoDict];
      }
      [messageDic safeSetObject:messageInfoArray forKey:@"messageInfoList"];
      break;
    }
    default:
      break;
  }

  return messageDic;
}

- (nullable ZIMMessageLiteInfo *)oZIMMessageLiteInfo:(nullable NSDictionary *)dict {
  if (!dict || [dict isEqual:[NSNull null]]) {
    return nil;
  }

  id msg = nil;

  ZIMMessageType type = (ZIMMessageType)[RCTConvert int:dict[@"type"]];
  switch (type) {
    case ZIMMessageTypeUnknown: {
      msg = [[ZIMMessageLiteInfo alloc] init];
      break;
    }
    case ZIMMessageTypeText: {
      msg = [[ZIMTextMessageLiteInfo alloc] init];
      ((ZIMTextMessageLiteInfo *)msg).message = [RCTConvert NSString:dict[@"message"]];
      break;
    }
    case ZIMMessageTypeRevoke: {
      msg = [[ZIMRevokeMessageLiteInfo alloc] init];
      break;
    }
    case ZIMMessageTypeCustom: {
      msg = [[ZIMCustomMessageLiteInfo alloc] init];
      ((ZIMCustomMessageLiteInfo *)msg).message = [RCTConvert NSString:dict[@"message"]];
      break;
    }
    case ZIMMessageTypeCombine: {
      msg = [[ZIMCombineMessageLiteInfo alloc] init];
      break;
    }
    case ZIMMessageTypeMultiple: {
      msg = [[ZIMMultipleMessageLiteInfo alloc] init];
      break;
    }
    case ZIMMessageTypeFile: {
      msg = [[ZIMFileMessageLiteInfo alloc] init];
      break;
    }
    case ZIMMessageTypeAudio: {
      msg = [[ZIMAudioMessageLiteInfo alloc] init];

      ((ZIMAudioMessageLiteInfo *)msg).audioDuration =
          (unsigned int)[RCTConvert NSUInteger:dict[@"audioDuration"]];
      break;
    }
    case ZIMMessageTypeVideo: {
      msg = [[ZIMVideoMessageLiteInfo alloc] init];
      ((ZIMVideoMessageLiteInfo *)msg).videoDuration =
          (unsigned int)[RCTConvert NSUInteger:dict[@"videoDuration"]];
      ((ZIMVideoMessageLiteInfo *)msg).videoFirstFrameDownloadUrl =
          [RCTConvert NSString:dict[@"videoFirstFrameDownloadUrl"]];
      [((ZIMVideoMessageLiteInfo *)msg) safeSetValue:[RCTConvert
                                                         NSString:dict[@"videoFirstFrameLocalPath"]]
                                              forKey:@"videoFirstFrameLocalPath"];
      [((ZIMVideoMessageLiteInfo *)msg)
          safeSetValue:@(CGSizeMake([RCTConvert NSUInteger:dict[@"videoFirstFrameWidth"]],
                                    [RCTConvert NSUInteger:dict[@"videoFirstFrameHeight"]]))
                forKey:@"videoFirstFrameSize"];
      break;
    }
    case ZIMMessageTypeImage: {
      msg = [[ZIMImageMessageLiteInfo alloc] init];
      ((ZIMImageMessageLiteInfo *)msg).thumbnailDownloadUrl =
          [RCTConvert NSString:dict[@"thumbnailDownloadUrl"]];
      ((ZIMImageMessageLiteInfo *)msg).largeImageDownloadUrl =
          [RCTConvert NSString:dict[@"largeImageDownloadUrl"]];
      [((ZIMImageMessageLiteInfo *)msg) safeSetValue:[RCTConvert
                                                         NSString:dict[@"thumbnailLocalPath"]]
                                              forKey:@"thumbnailLocalPath"];
      [((ZIMImageMessageLiteInfo *)msg) safeSetValue:[RCTConvert
                                                         NSString:dict[@"largeImageLocalPath"]]
                                              forKey:@"largeImageLocalPath"];
      [((ZIMImageMessageLiteInfo *)msg)
          safeSetValue:@(CGSizeMake([RCTConvert NSUInteger:dict[@"originalImageWidth"]],
                                    [RCTConvert NSUInteger:dict[@"originalImageHeight"]]))
                forKey:@"originalImageSize"];
      [((ZIMImageMessageLiteInfo *)msg)
          safeSetValue:@(CGSizeMake([RCTConvert NSUInteger:dict[@"thumbnailWidth"]],
                                    [RCTConvert NSUInteger:dict[@"thumbnailHeight"]]))
                forKey:@"thumbnailSize"];
      [((ZIMImageMessageLiteInfo *)msg)
          safeSetValue:@(CGSizeMake([RCTConvert NSUInteger:dict[@"largeImageWidth"]],
                                    [RCTConvert NSUInteger:dict[@"largeImageHeight"]]))
                forKey:@"largeImageSize"];
      break;
    }
    default:
      break;
  }

  [msg safeSetValue:[RCTConvert NSNumber:dict[@"type"]] forKey:@"type"];

  if ([msg isKindOfClass:[ZIMMediaMessageLiteInfo class]]) {
    [msg safeSetValue:[RCTConvert NSString:dict[@"fileLocalPath"]] forKey:@"fileLocalPath"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"fileDownloadUrl"]] forKey:@"fileDownloadUrl"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"fileName"]] forKey:@"fileName"];
    [msg safeSetValue:[RCTConvert NSNumber:dict[@"fileSize"]] forKey:@"fileSize"];
  } else if ([msg isKindOfClass:[ZIMCustomMessageLiteInfo class]]) {
    [msg safeSetValue:[RCTConvert NSNumber:dict[@"subType"]] forKey:@"subType"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"searchedContent"]] forKey:@"searchedContent"];
  } else if ([msg isKindOfClass:[ZIMCombineMessageLiteInfo class]]) {
    [msg safeSetValue:[RCTConvert NSString:dict[@"title"]] forKey:@"title"];
    [msg safeSetValue:[RCTConvert NSString:dict[@"summary"]] forKey:@"summary"];
  } else if ([msg isKindOfClass:[ZIMMultipleMessageLiteInfo class]]) {
    NSMutableArray *messageDictArray = dict[@"messageInfoList"];
    NSMutableArray<ZIMMessageLiteInfo *> *messageObjArray = [NSMutableArray array];
    for (NSDictionary *msgdict in messageDictArray) {
      ZIMMessageLiteInfo *message = [self oZIMMessageLiteInfo:msgdict];
      if (message) {
        [messageObjArray addObject:message];
      }
    }
    [msg safeSetValue:messageObjArray forKey:@"messageInfoList"];
  }

  return msg;
}

- (NSDictionary *)mZIMMessageRepliedInfo:(ZIMMessageRepliedInfo *)info {
  if (!info) {
    return nil;
  }

  NSMutableDictionary *infoDic = [NSMutableDictionary dictionary];
  [infoDic safeSetObject:[NSNumber numberWithInt:(int)info.state] forKey:@"state"];
  [infoDic safeSetObject:[NSNumber numberWithLongLong:info.messageSeq] forKey:@"messageSeq"];
  NSNumber *longlongMessageID = [NSNumber numberWithLongLong:info.messageID];
  [infoDic safeSetObject:[longlongMessageID stringValue] forKey:@"messageID"];
  [infoDic safeSetObject:info.senderUserID forKey:@"senderUserID"];
  [infoDic safeSetObject:[NSNumber numberWithUnsignedLongLong:info.sentTime] forKey:@"sentTime"];

  ZIMMessageLiteInfo *message = info.messageInfo;
  if (!message) {
    return infoDic;
  }

  [infoDic safeSetObject:[self mZIMMessageLiteInfo:message] forKey:@"messageInfo"];
  return infoDic;
}

- (NSArray<ZIMMessage *> *)oZIMMessageList:(NSArray<NSDictionary *> *)infos {
  NSMutableArray<ZIMMessage *> *resultArray = [NSMutableArray array];
  for (NSDictionary *dict in infos) {
    ZIMMessage *message = [self oZIMMessage:dict];
    if (message) {
      [resultArray addObject:message];
    }
  }

  return resultArray;
}

- (nullable ZIMConversation *)oZIMConversation:(nullable NSDictionary *)dict {
  if (!dict || [dict isEqual:[NSNull null]]) {
    return nil;
  }

  ZIMConversation *conversation = [[ZIMConversation alloc] init];
  conversation.conversationID = [RCTConvert NSString:dict[@"conversationID"]];
  conversation.conversationName = [RCTConvert NSString:dict[@"conversationName"]];
  conversation.conversationAlias = [RCTConvert NSString:dict[@"conversationAlias"]];
  conversation.conversationAvatarUrl = [RCTConvert NSString:dict[@"conversationAvatarUrl"]];
  conversation.type = (ZIMConversationType)[RCTConvert int:dict[@"type"]];
  conversation.notificationStatus =
      (ZIMConversationNotificationStatus)[RCTConvert int:dict[@"notificationStatus"]];
  conversation.unreadMessageCount =
      (unsigned int)[RCTConvert NSUInteger:dict[@"unreadMessageCount"]];
  conversation.orderKey = [RCTConvert int64_t:dict[@"orderKey"]];
  conversation.readMessageSeq = [RCTConvert int64_t:dict[@"readMessageSeq"]];
  [conversation safeSetValue:[RCTConvert NSNumber:dict[@"isPinned"]] forKey:@"isPinned"];
  conversation.pinnedTime = [RCTConvert int64_t:dict[@"pinnedTime"]];
  conversation.messageDestructDuration = [RCTConvert int:dict[@"messageDestructDuration"]];
  conversation.draft = [RCTConvert NSString:dict[@"draft"]];

  return conversation;
}

- (NSDictionary *)mZIMConversation:(ZIMConversation *)conversation {
  if (!conversation) {
    return nil;
  }

  NSNumber *type = [NSNumber numberWithInt:(int)conversation.type];

  NSMutableDictionary *conversationDic = [NSMutableDictionary dictionary];
  [conversationDic safeSetObject:conversation.conversationID forKey:@"conversationID"];
  [conversationDic safeSetObject:conversation.conversationName forKey:@"conversationName"];
  [conversationDic safeSetObject:conversation.conversationAlias forKey:@"conversationAlias"];
  [conversationDic safeSetObject:conversation.conversationAvatarUrl
                          forKey:@"conversationAvatarUrl"];
  [conversationDic safeSetObject:type forKey:@"type"];
  [conversationDic safeSetObject:[NSNumber numberWithInt:(int)conversation.notificationStatus]
                          forKey:@"notificationStatus"];
  [conversationDic safeSetObject:[NSNumber numberWithUnsignedInt:conversation.unreadMessageCount]
                          forKey:@"unreadMessageCount"];
  [conversationDic safeSetObject:[NSNumber numberWithLongLong:conversation.orderKey]
                          forKey:@"orderKey"];
  [conversationDic safeSetObject:[NSNumber numberWithLongLong:conversation.readMessageSeq]
                          forKey:@"readMessageSeq"];
  [conversationDic safeSetObject:[NSNumber numberWithBool:conversation.isPinned]
                          forKey:@"isPinned"];
  [conversationDic safeSetObject:[NSNumber numberWithLongLong:conversation.pinnedTime]
                          forKey:@"pinnedTime"];
  [conversationDic safeSetObject:[NSNumber numberWithInt:(int)conversation.messageDestructDuration]
                          forKey:@"messageDestructDuration"];
  [conversationDic safeSetObject:conversation.marks forKey:@"marks"];
  [conversationDic safeSetObject:conversation.draft forKey:@"draft"];
  [conversationDic safeSetObject:[self mZIMMessage:conversation.lastMessage] forKey:@"lastMessage"];

  if (conversation.mentionedInfoList != nil) {
    [conversationDic
        safeSetObject:[self mZIMMessageMentionedInfoList:conversation.mentionedInfoList]
               forKey:@"mentionedInfoList"];
  }

  [conversationDic safeSetObject:[NSNumber numberWithBool:conversation.isConversationDisabled]
                          forKey:@"isConversationDisabled"];
  [conversationDic safeSetObject:[NSNumber numberWithLongLong:conversation.selfMutedExpiredTime]
                          forKey:@"selfMutedExpiredTime"];
  if ([type intValue] == 2) {
    [conversationDic safeSetObject:[NSNumber numberWithBool:conversation.isConversationDisabled]
                            forKey:@"isDisabled"];
    [conversationDic safeSetObject:[NSNumber numberWithLongLong:conversation.selfMutedExpiredTime]
                            forKey:@"mutedExpiredTime"];
  }

  return conversationDic;
}

- (NSArray *)mZIMConversationList:(NSArray<ZIMConversation *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMConversation *info in infos) {
    [resultArray addObject:[self mZIMConversation:info]];
  }

  return resultArray;
}

- (NSArray *)mZIMMessageMentionedInfoList:(NSArray<ZIMMessageMentionedInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMMessageMentionedInfo *info in infos) {
    NSMutableDictionary *dic = [NSMutableDictionary dictionary];
    [dic safeSetObject:[NSNumber numberWithInt:(int)info.type] forKey:@"type"];
    [dic safeSetObject:info.fromUserID forKey:@"fromUserID"];
    [dic safeSetObject:[NSNumber numberWithInt:(int)info.messageSeq] forKey:@"messageSeq"];

    NSNumber *longlongMessageID = [NSNumber numberWithLongLong:info.messageID];
    [dic safeSetObject:[longlongMessageID stringValue] forKey:@"messageID"];

    [resultArray addObject:dic];
  }

  return resultArray;
}

- (NSDictionary *)mZIMMessageReaction:(ZIMMessageReaction *)info {
  if (!info) {
    return nil;
  }

  NSMutableDictionary *dic = [NSMutableDictionary dictionary];
  [dic safeSetObject:info.conversationID forKey:@"conversationID"];
  [dic safeSetObject:[NSNumber numberWithInt:(int)info.conversationType]
              forKey:@"conversationType"];
  [dic safeSetObject:[[NSNumber numberWithLongLong:info.messageID] stringValue]
              forKey:@"messageID"];
  [dic safeSetObject:info.reactionType forKey:@"reactionType"];
  [dic safeSetObject:[NSNumber numberWithBool:info.isSelfIncluded] forKey:@"isSelfIncluded"];
  [dic safeSetObject:[NSNumber numberWithUnsignedInt:info.totalCount] forKey:@"totalCount"];
  [dic safeSetObject:[NSNumber numberWithUnsignedInt:info.sumCount] forKey:@"sumCount"];
  [dic safeSetObject:[self mZIMMessageReactionUserInfoList:info.userList] forKey:@"userList"];

  return dic;
}

- (NSArray *)mNumberToStringList:(NSArray<NSNumber *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (NSNumber *info in infos) {
    [resultArray addObject:[info stringValue]];
  }

  return resultArray;
}

- (NSDictionary *)mZIMGroupMemberInfo:(ZIMGroupMemberInfo *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"type"] = @(info.type);
  resultMap[@"userID"] = info.userID;
  resultMap[@"userName"] = info.userName;
  resultMap[@"userAvatarUrl"] = info.userAvatarUrl;
  resultMap[@"userExtendedData"] = info.userExtendedData;

  resultMap[@"memberRole"] = @(info.memberRole);
  resultMap[@"memberNickname"] = info.memberNickname;
  resultMap[@"muteExpiredTime"] = @(info.muteExpiredTime);

  if (info.groupEnterInfo != nil) {
    NSMutableDictionary *infoMap = [[NSMutableDictionary alloc] init];
    infoMap[@"enterTime"] = @(info.groupEnterInfo.enterTime);
    infoMap[@"enterType"] = @(info.groupEnterInfo.enterType);
    infoMap[@"operatedUser"] = [self mZIMUserOrGroupMember:info.groupEnterInfo.operatedUser];

    resultMap[@"groupEnterInfo"] = infoMap;
  }

  return resultMap;
}

- (NSDictionary *)mZIMCommunityMemberInfo:(ZIMCommunityMemberInfo *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"type"] = @(info.type);
  resultMap[@"userID"] = info.userID;
  resultMap[@"userName"] = info.userName;
  resultMap[@"userAvatarUrl"] = info.userAvatarUrl;
  resultMap[@"userExtendedData"] = info.userExtendedData;

  resultMap[@"memberRole"] = @(info.memberRole);

  if (info.enterInfo != nil) {
    NSMutableDictionary *infoMap = [[NSMutableDictionary alloc] init];
    infoMap[@"enterTime"] = @(info.enterInfo.enterTime);
    infoMap[@"enterType"] = @(info.enterInfo.enterType);
    infoMap[@"operatedUserID"] = info.enterInfo.operatedUserID;

    resultMap[@"enterInfo"] = infoMap;
  }

  if (info.muteInfo != nil) {
    NSMutableDictionary *infoMap = [[NSMutableDictionary alloc] init];
    infoMap[@"muteType"] = @(info.muteInfo.muteType);
    infoMap[@"muteExpiredTime"] = @(info.muteInfo.muteExpiredTime);
    infoMap[@"channelID"] = info.muteInfo.channelID;

    resultMap[@"muteInfo"] = infoMap;
  }

  return resultMap;
}

- (NSDictionary *)mZIMCommunityMemberSimpleInfo:(ZIMCommunityMemberSimpleInfo *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"type"] = @(info.type);
  resultMap[@"userID"] = info.userID;
  resultMap[@"userName"] = info.userName;
  resultMap[@"userAvatarUrl"] = info.userAvatarUrl;
  resultMap[@"userExtendedData"] = info.userExtendedData;

  resultMap[@"memberRole"] = @(info.memberRole);

  return resultMap;
}

- (NSDictionary *)mZIMGroupInfo:(ZIMGroupInfo *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"groupID"] = info.groupID;
  resultMap[@"groupName"] = info.groupName;
  resultMap[@"groupAvatarUrl"] = info.groupAvatarUrl;

  return resultMap;
}

- (NSDictionary *)mZIMGroupFullInfo:(ZIMGroupFullInfo *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"baseInfo"] = [self mZIMGroupInfo:info.baseInfo];
  resultMap[@"groupAlias"] = info.groupAlias;
  resultMap[@"groupNotice"] = info.groupNotice;
  resultMap[@"groupAttributes"] = info.groupAttributes;
  resultMap[@"notificationStatus"] = @(info.notificationStatus);
  resultMap[@"maxMemberCount"] = @(info.maxMemberCount);
  resultMap[@"createTime"] = @(info.createTime);
  resultMap[@"mutedInfo"] = [self mZIMGroupMuteInfo:info.mutedInfo];
  resultMap[@"verifyInfo"] = [self mZIMGroupVerifyInfo:info.verifyInfo];

  return resultMap;
}

- (NSDictionary *)mZIMCommunityInfo:(ZIMCommunityInfo *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"communityID"] = info.communityID;
  resultMap[@"communityName"] = info.communityName;
  resultMap[@"communityAvatarUrl"] = info.communityAvatarUrl;

  return resultMap;
}

- (NSDictionary *)mZIMCommunity:(ZIMCommunity *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"baseInfo"] = [self mZIMCommunityInfo:info.baseInfo];
  resultMap[@"totalUnreadMessageCount"] = @(info.totalUnreadMessageCount);

  return resultMap;
}

- (NSDictionary *)mZIMCommunityFullInfo:(ZIMCommunityFullInfo *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"baseInfo"] = [self mZIMCommunityInfo:info.baseInfo];
  resultMap[@"communityNotice"] = info.communityNotice;
  resultMap[@"communityAttributes"] = info.communityAttributes;
  resultMap[@"notificationStatus"] = @(info.notificationStatus);
  resultMap[@"currentMemberCount"] = @(info.currentMemberCount);
  resultMap[@"createTime"] = @(info.createTime);
  resultMap[@"creatorUserID"] = info.creatorUserID;

  return resultMap;
}

- (NSDictionary *)mZIMCommunityChannelInfo:(ZIMCommunityChannelInfo *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"communityID"] = info.communityID;
  resultMap[@"channelType"] = @(info.channelType);
  resultMap[@"channelID"] = info.channelID;
  resultMap[@"channelName"] = info.channelName;
  resultMap[@"channelAvatarUrl"] = info.channelAvatarUrl;

  return resultMap;
}

- (NSDictionary *)mZIMCommunityChannel:(ZIMCommunityChannel *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"baseInfo"] = [self mZIMCommunityChannelInfo:info.baseInfo];
  resultMap[@"conversationID"] = info.conversationID;
  resultMap[@"channelAttributes"] = info.channelAttributes;
  resultMap[@"unreadMessageCount"] = @(info.unreadMessageCount);
  resultMap[@"notificationStatus"] = @(info.notificationStatus);
  resultMap[@"selfMutedExpiredTime"] = @(info.selfMutedExpiredTime);
  resultMap[@"draft"] = info.draft;

  if (info.lastMessage != nil) {
    [resultMap safeSetObject:[self mZIMMessage:info.lastMessage] forKey:@"lastMessage"];
  }
  if (info.mentionedInfoList != nil) {
    [resultMap safeSetObject:[self mZIMMessageMentionedInfoList:info.mentionedInfoList]
                      forKey:@"mentionedInfoList"];
  }

  return resultMap;
}

- (NSDictionary *)mZIMCommunityChannelFullInfo:(ZIMCommunityChannelFullInfo *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"baseInfo"] = [self mZIMCommunityChannelInfo:info.baseInfo];
  resultMap[@"channelNotice"] = info.channelNotice;
  resultMap[@"channelAttributes"] = info.channelAttributes;
  resultMap[@"currentMemberCount"] = @(info.currentMemberCount);
  resultMap[@"createTime"] = @(info.createTime);
  resultMap[@"creatorUserID"] = info.creatorUserID;

  if (info.muteInfo != nil) {
    resultMap[@"muteInfo"] = [self mZIMCommunityChannelMuteInfo:info.muteInfo];
  }

  return resultMap;
}

- (NSArray *)mZIMCommunityChannelFullInfoList:(NSArray<ZIMCommunityChannelFullInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMCommunityChannelFullInfo *info in infos) {
    [resultArray addObject:[self mZIMCommunityChannelFullInfo:info]];
  }

  return resultArray;
}

- (NSArray *)mZIMCommunityChannelList:(NSArray<ZIMCommunityChannel *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMCommunityChannel *info in infos) {
    [resultArray addObject:[self mZIMCommunityChannel:info]];
  }

  return resultArray;
}

- (NSDictionary *)mZIMGroup:(ZIMGroup *)group {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"baseInfo"] = [self mZIMGroupInfo:group.baseInfo];
  resultMap[@"groupAlias"] = group.groupAlias;
  resultMap[@"notificationStatus"] = @(group.notificationStatus);

  return resultMap;
}

- (NSArray *)mZIMGroupList:(NSArray<ZIMGroup *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMGroup *info in infos) {
    [resultArray addObject:[self mZIMGroup:info]];
  }

  return resultArray;
}

- (NSDictionary *)mZIMGroupOperatedInfo:(ZIMGroupOperatedInfo *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"userID"] = info.userID;
  resultMap[@"userName"] = info.userName;
  resultMap[@"memberRole"] = @(info.memberRole);
  resultMap[@"memberNickname"] = info.memberNickname;

  return resultMap;
}

- (NSArray *)mZIMGroupMemberInfoList:(NSArray<ZIMGroupMemberInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMGroupMemberInfo *info in infos) {
    [resultArray addObject:[self mZIMGroupMemberInfo:info]];
  }

  return resultArray;
}

- (NSArray *)mZIMCommunityMemberInfoList:(NSArray<ZIMCommunityMemberInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMCommunityMemberInfo *info in infos) {
    [resultArray addObject:[self mZIMCommunityMemberInfo:info]];
  }

  return resultArray;
}

- (NSDictionary *)mZIMGroupMuteInfo:(ZIMGroupMuteInfo *)info {
  if (!info) {
    return nil;
  }

  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"mode"] = @(info.mode);
  resultMap[@"expiredTime"] = @(info.expiredTime);
  resultMap[@"roles"] = info.roles;

  return resultMap;
}

- (NSDictionary *)mZIMCommunityChannelMuteInfo:(ZIMCommunityChannelMuteInfo *)info {
  if (!info) {
    return nil;
  }

  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"mode"] = @(info.mode);
  resultMap[@"expiredTime"] = @(info.expiredTime);
  resultMap[@"roles"] = info.roles;

  return resultMap;
}

- (NSDictionary *)mZIMGroupVerifyInfo:(ZIMGroupVerifyInfo *)info {
  if (!info) {
    return nil;
  }

  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"joinMode"] = @(info.joinMode);
  resultMap[@"inviteMode"] = @(info.inviteMode);
  resultMap[@"beInviteMode"] = @(info.beInviteMode);

  return resultMap;
}

- (nullable ZIMVoIPConfig *)oZIMVoIPConfig:(nullable NSDictionary *)dict {
  if (!dict || [dict isEqual:[NSNull null]]) {
    return nil;
  }

  ZIMVoIPConfig *config = [[ZIMVoIPConfig alloc] init];
  config.iOSVoIPHandleType = (ZIMCXHandleType)[RCTConvert int:dict[@"iOSVoIPHandleType"]];
  config.iOSVoIPHandleValue = [RCTConvert NSString:dict[@"iOSVoIPHandleValue"]];
  [config safeSetValue:[RCTConvert NSNumber:dict[@"iOSVoIPHasVideo"]] forKey:@"iOSVoIPHasVideo"];

  return config;
}

- (nullable ZIMOfflinePushPrivateMessageTemplate *)oZIMOfflinePushPrivateMessageTemplate:
    (nullable NSDictionary *)dict {
  if (!dict || [dict isEqual:[NSNull null]]) {
    return nil;
  }

  ZIMOfflinePushPrivateMessageTemplate *config =
      [[ZIMOfflinePushPrivateMessageTemplate alloc] init];
  config.templateID = [RCTConvert NSString:dict[@"templateID"]];
  config.titleParameters = [RCTConvert NSString:dict[@"titleParameters"]];
  config.contentParameters = [RCTConvert NSString:dict[@"contentParameters"]];

  return config;
}

- (nullable ZIMPushConfig *)mZIMPushConfig:(NSDictionary *)config {
  NSDictionary *dict = [RCTConvert NSDictionary:config[@"pushConfig"]];
  if (!dict || [dict isEqual:[NSNull null]]) {
    return nil;
  } else {
    ZIMPushConfig *pushConfig = [[ZIMPushConfig alloc] init];
    pushConfig.title = [RCTConvert NSString:dict[@"title"]];
    pushConfig.content = [RCTConvert NSString:dict[@"content"]];
    pushConfig.payload = [RCTConvert NSString:dict[@"payload"]];
    pushConfig.resourcesID = [RCTConvert NSString:dict[@"resourcesID"]];
    pushConfig.threadID = [RCTConvert NSString:dict[@"threadID"]];
    [pushConfig safeSetValue:[RCTConvert NSNumber:dict[@"badgeIncrement"]]
                      forKey:@"badgeIncrement"];
    [pushConfig safeSetValue:[RCTConvert NSNumber:dict[@"enableBadge"]] forKey:@"enableBadge"];
    pushConfig.voIPConfig = [self oZIMVoIPConfig:dict[@"voIPConfig"]];
    pushConfig.privateMessageTemplate =
        [self oZIMOfflinePushPrivateMessageTemplate:dict[@"privateMessageTemplate"]];
    return pushConfig;
  }
}

- (NSArray *)mZIMErrorUserInfoList:(NSArray<ZIMErrorUserInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMErrorUserInfo *info in infos) {
    [resultArray addObject:@{@"userID" : info.userID, @"reason" : @(info.reason)}];
  }

  return resultArray;
}

- (nullable NSDictionary *)mZIMUserStatus:(nullable ZIMUserStatus *)status {
  if (status == nil || [status isEqual:[NSNull null]]) {
    return nil;
  }
  NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
  [dic setObject:status.userID forKey:@"userID"];
  [dic setObject:status.customStatus forKey:@"customStatus"];
  [dic setObject:@(status.onlineStatus) forKey:@"onlineStatus"];
  [dic setObject:status.onlinePlatforms forKey:@"onlinePlatforms"];
  [dic setObject:@(status.lastUpdateTime) forKey:@"lastUpdateTime"];
  [dic setObject:@(status.lastOnlineStatusUpdateTime) forKey:@"lastOnlineStatusUpdateTime"];
  [dic setObject:@(status.lastCustomStatusUpdateTime) forKey:@"lastCustomStatusUpdateTime"];
  return dic;
}

- (nullable NSArray<NSDictionary *> *)mZIMUserStatusList:
    (nullable NSArray<ZIMUserStatus *> *)statusList {
  if (statusList == nil || [statusList isEqual:[NSNull null]]) {
    return nil;
  }
  NSMutableArray<NSDictionary *> *basicList = [[NSMutableArray alloc] init];
  for (ZIMUserStatus *userStatus in statusList) {
    NSDictionary *dictionary = [self mZIMUserStatus:userStatus];
    [basicList addObject:dictionary];
  }
  return basicList;
}

- (nullable NSDictionary *)mZIMUserStatusSubsciption:
    (nullable ZIMUserStatusSubscription *)subscription {
  if (subscription == nil || [subscription isEqual:[NSNull null]]) {
    return nil;
  }
  NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
  [dic setObject:[self mZIMUserStatus:subscription.userStatus] forKey:@"userStatus"];
  [dic setObject:@(subscription.subscribeExpiredTime) forKey:@"subscribeExpiredTime"];
  return dic;
}

- (nullable NSArray<NSDictionary *> *)mZIMUserStatusSubsciptionList:
    (nullable NSArray<ZIMUserStatusSubscription *> *)subscriptionList {
  if (subscriptionList == nil || [subscriptionList isEqual:[NSNull null]]) {
    return nil;
  }
  NSMutableArray<NSDictionary *> *basicList = [[NSMutableArray alloc] init];
  for (ZIMUserStatusSubscription *subscription in subscriptionList) {
    NSDictionary *dictionary = [self mZIMUserStatusSubsciption:subscription];
    [basicList addObject:dictionary];
  }
  return basicList;
}

- (NSArray *)mZIMCallUserList:(NSArray<ZIMCallUserInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMCallUserInfo *info in infos) {
    [resultArray addObject:@{
      @"userID" : info.userID,
      @"state" : @(info.state),
      @"extendedData" : info.extendedData
    }];
  }

  return resultArray;
}

- (NSDictionary *)mZIMGroupAttributesUpdateInfo:(ZIMGroupAttributesUpdateInfo *)info {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

  resultMap[@"action"] = @(info.action);
  resultMap[@"groupAttributes"] = info.groupAttributes;

  return resultMap;
}

- (NSDictionary *)mZIMRoomMemberAttributesInfo:(ZIMRoomMemberAttributesInfo *)info {
  NSMutableDictionary *resultMap = [NSMutableDictionary dictionary];
  resultMap[@"userID"] = info.userID;
  resultMap[@"attributes"] = info.attributes;

  return resultMap;
}

- (NSDictionary *)mZIMRoomMemberAttributesOperatedInfo:(ZIMRoomMemberAttributesOperatedInfo *)info {
  NSMutableDictionary *resultMap = [NSMutableDictionary dictionary];

  resultMap[@"attributesInfo"] = [self mZIMRoomMemberAttributesInfo:info.attributesInfo];
  resultMap[@"errorKeys"] = info.errorKeys;

  return resultMap;
}

- (NSDictionary *)mZIMRoomMemberAttributesUpdateInfo:(ZIMRoomMemberAttributesUpdateInfo *)info {
  NSMutableDictionary *resultMap = [NSMutableDictionary dictionary];

  resultMap[@"attributesInfo"] = [self mZIMRoomMemberAttributesInfo:info.attributesInfo];

  return resultMap;
}

- (NSArray *)mZIMRoomMemberAttributesOperatedInfoList:
    (NSArray<ZIMRoomMemberAttributesOperatedInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMRoomMemberAttributesOperatedInfo *info in infos) {
    NSDictionary *infoObj = [self mZIMRoomMemberAttributesOperatedInfo:info];
    [resultArray addObject:infoObj];
  }

  return resultArray;
}

- (NSArray *)mZIMRoomMemberAttributesUpdateInfoList:
    (NSArray<ZIMRoomMemberAttributesUpdateInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMRoomMemberAttributesUpdateInfo *info in infos) {
    NSDictionary *infoObj = [self mZIMRoomMemberAttributesUpdateInfo:info];
    [resultArray addObject:infoObj];
  }

  return resultArray;
}

- (NSArray *)mZIMRoomMemberAttributesInfoList:(NSArray<ZIMRoomMemberAttributesInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMRoomMemberAttributesInfo *info in infos) {
    NSDictionary *infoObj = [self mZIMRoomMemberAttributesInfo:info];
    [resultArray addObject:infoObj];
  }

  return resultArray;
}

- (NSArray *)mZIMMessageReceiptInfoList:(NSArray<ZIMMessageReceiptInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMMessageReceiptInfo *info in infos) {
    NSMutableDictionary *dic = [NSMutableDictionary dictionary];
    [dic safeSetObject:info.conversationID forKey:@"conversationID"];
    [dic safeSetObject:[NSNumber numberWithInt:(int)info.conversationType]
                forKey:@"conversationType"];
    [dic safeSetObject:[NSNumber numberWithInt:(int)info.status] forKey:@"status"];
    [dic safeSetObject:[NSNumber numberWithUnsignedInt:info.readMemberCount]
                forKey:@"readMemberCount"];
    [dic safeSetObject:[NSNumber numberWithUnsignedInt:info.unreadMemberCount]
                forKey:@"unreadMemberCount"];
    [dic safeSetObject:[NSNumber numberWithUnsignedInt:info.deliveredMemberCount]
                forKey:@"deliveredMemberCount"];
    [dic safeSetObject:[[NSNumber numberWithLongLong:info.messageID] stringValue]
                forKey:@"messageID"];
    [dic safeSetObject:[NSNumber numberWithBool:info.isSelfOperated] forKey:@"isSelfOperated"];
    [dic safeSetObject:[NSNumber numberWithUnsignedLongLong:info.readTime] forKey:@"readTime"];

    [resultArray addObject:dic];
  }

  return resultArray;
}

- (NSArray *)mZIMMessageList:(NSArray<ZIMMessage *> *)infos {
  if (!infos) {
    return nil;
  }

  NSMutableArray *messageArray = [NSMutableArray array];
  for (ZIMMessage *message in infos) {
    NSDictionary *msgDict = [self mZIMMessage:message];
    [messageArray addObject:msgDict];
  }
  return messageArray;
}

- (NSArray *)mZIMMessageReactionList:(NSArray<ZIMMessageReaction *> *)infos {
  if (!infos) {
    return nil;
  }

  NSMutableArray *messageArray = [NSMutableArray array];
  for (ZIMMessageReaction *info in infos) {
    NSDictionary *msgDict = [self mZIMMessageReaction:info];
    [messageArray addObject:msgDict];
  }
  return messageArray;
}

- (NSArray *)mZIMMessageReactionUserInfoList:(NSArray<ZIMMessageReactionUserInfo *> *)infos {
  if (!infos) {
    return nil;
  }

  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMMessageReactionUserInfo *info in infos) {
    [resultArray addObject:@{@"userID" : info.userID}];
  }

  return resultArray;
}

- (NSDictionary *)mZIMUserInfo:(ZIMUserInfo *)info {
  NSMutableDictionary *resultMap = [NSMutableDictionary dictionary];

  resultMap[@"type"] = @(info.type);
  resultMap[@"userID"] = info.userID;
  resultMap[@"userName"] = info.userName;
  resultMap[@"userAvatarUrl"] = info.userAvatarUrl;
  resultMap[@"userExtendedData"] = info.userExtendedData;

  return resultMap;
}

- (NSDictionary *)mZIMUserRule:(ZIMUserRule *)info {
  NSMutableDictionary *resultMap = [NSMutableDictionary dictionary];

  NSMutableDictionary *infoMap = [NSMutableDictionary dictionary];
  infoMap[@"onlinePlatforms"] = info.offlinePushRule.onlinePlatforms;
  infoMap[@"notToReceiveOfflinePushPlatforms"] =
      info.offlinePushRule.notToReceiveOfflinePushPlatforms;

  resultMap[@"offlinePushRule"] = infoMap;

  return resultMap;
}

- (NSDictionary *)mZIMUserFullInfo:(ZIMUserFullInfo *)info {
  NSMutableDictionary *resultMap = [NSMutableDictionary dictionary];
  [resultMap safeSetObject:[self mZIMUserInfo:info.baseInfo] forKey:@"baseInfo"];

  return resultMap;
}

- (NSArray *)mZIMUserInfoList:(NSArray<ZIMUserInfo *> *)infos {
  NSMutableArray *userArray = [NSMutableArray array];
  for (ZIMUserInfo *info in infos) {
    [userArray addObject:[self mZIMUserInfo:info]];
  }

  return userArray;
}

- (NSDictionary *)mZIMFriendInfo:(ZIMFriendInfo *)info {
  NSMutableDictionary *resultMap = [NSMutableDictionary dictionary];

  resultMap[@"type"] = @(info.type);
  resultMap[@"userID"] = info.userID;
  resultMap[@"userName"] = info.userName;
  resultMap[@"userAvatarUrl"] = info.userAvatarUrl;
  resultMap[@"userExtendedData"] = info.userExtendedData;

  resultMap[@"wording"] = info.wording;
  resultMap[@"createTime"] = @(info.createTime);
  resultMap[@"friendAlias"] = info.friendAlias;
  resultMap[@"friendAttributes"] = info.friendAttributes;

  return resultMap;
}

- (NSArray *)mZIMFriendInfoList:(NSArray<ZIMFriendInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMFriendInfo *info in infos) {
    [resultArray addObject:[self mZIMFriendInfo:info]];
  }

  return resultArray;
}

- (NSArray *)mZIMFriendApplicationList:(NSArray<ZIMFriendApplicationInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMFriendApplicationInfo *info in infos) {
    [resultArray addObject:@{
      @"type" : @(info.type),
      @"state" : @(info.state),
      @"wording" : info.wording,
      @"createTime" : @(info.createTime),
      @"updateTime" : @(info.updateTime),
      @"applyUser" : [self mZIMUserInfo:info.applyUser],
    }];
  }

  return resultArray;
}

- (NSArray *)mZIMGroupApplicationList:(NSArray<ZIMGroupApplicationInfo *> *)infos {
  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMGroupApplicationInfo *info in infos) {
    [resultArray addObject:@{
      @"type" : @(info.type),
      @"state" : @(info.state),
      @"wording" : info.wording,
      @"createTime" : @(info.createTime),
      @"updateTime" : @(info.updateTime),
      @"applyUser" : [self mZIMUserInfo:info.applyUser],
      @"groupInfo" : [self mZIMGroupInfo:info.groupInfo],
      @"operatedUser" : !info.operatedUser ? [NSNull null]
                                           : [self mZIMUserOrGroupMember:info.operatedUser],
    }];
  }

  return resultArray;
}

- (NSDictionary *)mZIMUserOrGroupMember:(ZIMUserInfo *)info {
  if (!info) {
    return nil;
  }

  NSMutableDictionary *resultMap = [NSMutableDictionary dictionary];

  resultMap[@"type"] = @(info.type);
  resultMap[@"userID"] = info.userID;
  resultMap[@"userName"] = info.userName;
  resultMap[@"userAvatarUrl"] = info.userAvatarUrl;
  resultMap[@"userExtendedData"] = info.userExtendedData;

  if ([info isKindOfClass:[ZIMGroupMemberSimpleInfo class]]) {
    ZIMGroupMemberSimpleInfo *member = (ZIMGroupMemberSimpleInfo *)info;
    resultMap[@"memberRole"] = @(member.memberRole);
    resultMap[@"memberNickname"] = member.memberNickname;
  }

  return resultMap;
}

- (NSArray *)mZIMUserOrGroupMemberList:(NSArray<ZIMUserInfo *> *)infos {
  if (!infos) {
    return nil;
  }

  NSMutableArray *resultArray = [NSMutableArray array];

  for (ZIMUserInfo *info in infos) {
    [resultArray addObject:[self mZIMUserOrGroupMember:info]];
  }

  return resultArray;
}

- (NSDictionary *)mZIMTipsMessageChangeInfo:(ZIMTipsMessageChangeInfo *)info {
  if (!info) {
    return nil;
  }

  NSMutableDictionary *resultMap = [NSMutableDictionary dictionary];
  resultMap[@"type"] = @(info.type);

  if ([info isKindOfClass:[ZIMTipsMessageGroupChangeInfo class]]) {
    ZIMTipsMessageGroupChangeInfo *group = (ZIMTipsMessageGroupChangeInfo *)info;
    resultMap[@"groupDataFlag"] = @(group.groupDataFlag);
    resultMap[@"groupName"] = group.groupName;
    resultMap[@"groupAvatarUrl"] = group.groupAvatarUrl;
    resultMap[@"groupNotice"] = group.groupNotice;
    resultMap[@"groupMutedInfo"] = [self mZIMGroupMuteInfo:group.groupMutedInfo];
  } else if ([info isKindOfClass:[ZIMTipsMessageGroupMemberChangeInfo class]]) {
    ZIMTipsMessageGroupMemberChangeInfo *member = (ZIMTipsMessageGroupMemberChangeInfo *)info;
    resultMap[@"memberRole"] = @(member.memberRole);
    resultMap[@"muteExpiredTime"] = @(member.muteExpiredTime);
    resultMap[@"groupNewOwner"] =
        !member.groupNewOwner ? [NSNull null] : [self mZIMUserOrGroupMember:member.groupNewOwner];
  } else if ([info isKindOfClass:[ZIMTipsMessagePinStatusChangeInfo class]]) {
    ZIMTipsMessagePinStatusChangeInfo *pinMsg = (ZIMTipsMessagePinStatusChangeInfo *)info;
    [resultMap safeSetObject:[NSNumber numberWithBool:pinMsg.isPinned] forKey:@"isPinned"];
  }

  return resultMap;
}

@end
