#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;
}

- (NSArray<NSString *> *)supportedEvents {
  return @[
    RN_EVENT(@"error"),
    RN_EVENT(@"connectionStateChanged"),
    RN_EVENT(@"tokenWillExpire"),
    RN_EVENT(@"userInfoUpdated"),
    RN_EVENT(@"userRuleUpdated"),
    RN_EVENT(@"userStatusUpdated"),
    RN_EVENT(@"conversationsAllDeleted"),
    RN_EVENT(@"conversationChanged"),
    RN_EVENT(@"conversationTotalUnreadMessageCountUpdated"),
    RN_EVENT(@"conversationMessageReceiptChanged"),
    RN_EVENT(@"messageReceiptChanged"),
    RN_EVENT(@"messageRevokeReceived"),
    RN_EVENT(@"messageReactionsChanged"),
    RN_EVENT(@"messageRepliedCountChanged"),
    RN_EVENT(@"messageRepliedInfoChanged"),
    RN_EVENT(@"messageEdited"),
    RN_EVENT(@"messageDeleted"),
    RN_EVENT(@"messageSentStatusChanged"),
    RN_EVENT(@"receivePeerMessage"),
    RN_EVENT(@"receiveGroupMessage"),
    RN_EVENT(@"receiveRoomMessage"),
    RN_EVENT(@"peerMessageReceived"),
    RN_EVENT(@"roomMessageReceived"),
    RN_EVENT(@"groupMessageReceived"),
    RN_EVENT(@"broadcastMessageReceived"),
    RN_EVENT(@"messageAttachedHook"),
    RN_EVENT(@"messageLoadingProgress"),
    RN_EVENT(@"messagePortProgress"),
    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(@"callInvitationCreated"),
    RN_EVENT(@"callInvitationReceived"),
    RN_EVENT(@"callInvitationCancelled"),
    RN_EVENT(@"callInvitationTimeout"),
    RN_EVENT(@"callInvitationEnded"),
    RN_EVENT(@"callUserStateChanged"),
    RN_EVENT(@"callInvitationAccepted"),
    RN_EVENT(@"callInvitationRejected"),
    RN_EVENT(@"callInviteesAnsweredTimeout"),
    RN_EVENT(@"blacklistChanged"),
    RN_EVENT(@"friendListChanged"),
    RN_EVENT(@"friendInfoUpdated"),
    RN_EVENT(@"friendApplicationListChanged"),
    RN_EVENT(@"friendApplicationUpdated")
  ];
}

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

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(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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim uploadLog:^(ZIMError *_Nonnull errorInfo) {
    if (errorInfo.code == 0) {
      resolve(nil);
    } else {
      reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
    }
  }];
}

RCT_EXPORT_METHOD(login
                  : (NSString *)handle userID
                  : (NSString *)userID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim
      loginWithUserID:userID
               config:configObj
             callback:^(ZIMError *_Nonnull errorInfo) {
               if (errorInfo.code == 0) {
                 resolve(nil);
               } else {
                 reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
               }
             }];
}

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

  [zim logout];

  resolve(nil);
}

RCT_EXPORT_METHOD(renewToken
                  : (NSString *)handle token
                  : (NSString *)token resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim renewToken:token
         callback:^(NSString *_Nonnull token, ZIMError *_Nonnull errorInfo) {
           if (errorInfo.code == 0) {
             resolve(@{@"token" : token});
           } else {
             reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
           }
         }];
}

RCT_EXPORT_METHOD(updateUserName
                  : (NSString *)handle userName
                  : (NSString *)userName resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim updateUserName:userName
             callback:^(NSString *_Nonnull retUserName, ZIMError *_Nonnull errorInfo) {
               if (errorInfo.code == 0) {
                 resolve(@{@"userName" : retUserName});
               } else {
                 reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
               }
             }];
}

RCT_EXPORT_METHOD(updateUserAvatarUrl
                  : (NSString *)handle userAvatarUrl
                  : (NSString *)userAvatarUrl resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim updateUserAvatarUrl:userAvatarUrl
                  callback:^(NSString *_Nonnull userAvatarUrl, ZIMError *_Nonnull errorInfo) {
                    if (errorInfo.code == 0) {
                      resolve(@{@"userAvatarUrl" : userAvatarUrl});
                    } else {
                      reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                             nil);
                    }
                  }];
}

RCT_EXPORT_METHOD(updateUserExtendedData
                  : (NSString *)handle extendedData
                  : (NSString *)extendedData resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim updateUserExtendedData:extendedData
                     callback:^(NSString *_Nonnull retExtendedData, ZIMError *_Nonnull errorInfo) {
                       if (errorInfo.code == 0) {
                         resolve(@{@"extendedData" : retExtendedData});
                       } else {
                         reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                errorInfo.message, nil);
                       }
                     }];
}

RCT_EXPORT_METHOD(updateUserCustomStatus
                  : (NSString *)handle customStatus
                  : (NSString *)customStatus resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim updateUserCustomStatus:customStatus
                     callback:^(NSString *_Nonnull retcustomStatus, ZIMError *_Nonnull errorInfo) {
                       if (errorInfo.code == 0) {
                         resolve(@{@"customStatus" : retcustomStatus});
                       } else {
                         reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                errorInfo.message, nil);
                       }
                     }];
}

RCT_EXPORT_METHOD(updateUserOfflinePushRule
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull info,
                                   ZIMError *_Nonnull errorInfo) {
                          if (errorInfo.code == 0) {
                            NSMutableDictionary *infoMap = [NSMutableDictionary dictionary];
                            infoMap[@"onlinePlatforms"] = info.onlinePlatforms;
                            infoMap[@"notToReceiveOfflinePushPlatforms"] =
                                info.notToReceiveOfflinePushPlatforms;
                            resolve(@{@"offlinePushRule" : infoMap});
                          } else {
                            reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                   errorInfo.message, nil);
                          }
                        }];
}

RCT_EXPORT_METHOD(queryUsersInfo
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

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

                 resolve(@{
                   @"userList" : userInfoArray,
                   @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                 });
               } else {
                 reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
               }
             }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

    [zim subscribeUsersStatus:userIDs
                       config:config
                     callback:^(NSArray<ZIMErrorUserInfo *> *_Nonnull errorUserList,
                                ZIMError *_Nonnull errorInfo) {
                       if (errorInfo.code == 0) {
                         resolve(@{@"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]});
                       } else {
                         reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                errorInfo.message, nil);
                       }
                     }];

  } else {
    [zim
        unsubscribeUsersStatus:userIDs
                      callback:^(NSArray<ZIMErrorUserInfo *> *_Nonnull errorUserList,
                                 ZIMError *_Nonnull errorInfo) {
                        if (errorInfo.code == 0) {
                          resolve(@{@"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]});
                        } else {
                          reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                 errorInfo.message, nil);
                        }
                      }];
  }
}

RCT_EXPORT_METHOD(queryUsersStatus
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }
  [zim queryUsersStatusByUserIDs:userIDs
                        callback:^(NSArray<ZIMUserStatus *> *_Nonnull userStatusList,
                                   NSArray<ZIMErrorUserInfo *> *_Nonnull errorUserList,
                                   ZIMError *_Nonnull errorInfo) {
                          if (errorInfo.code == 0) {
                            resolve(@{
                              @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList],
                              @"userStatusList" : [self mZIMUserStatusList:userStatusList]
                            });
                          } else {
                            reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                   errorInfo.message, nil);
                          }
                        }];
}

RCT_EXPORT_METHOD(querySubscribedUsersStatus
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim querySubscribedUserStatusListWithConfig:config
                                      callback:^(NSArray<ZIMUserStatusSubscription *>
                                                     *_Nonnull userStatusSubscriptionList,
                                                 ZIMError *_Nonnull errorInfo) {
                                        if (errorInfo.code == 0) {
                                          resolve(@{
                                            @"userStatusSubscriptionList" :
                                                [self mZIMUserStatusSubsciptionList:
                                                          userStatusSubscriptionList]
                                          });
                                        } else {
                                          reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                                 errorInfo.message, nil);
                                        }
                                      }];
}

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

  [zim querySelfUserInfo:^(ZIMSelfUserInfo *_Nonnull info, ZIMError *_Nonnull errorInfo) {
    if (errorInfo.code == 0) {
      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});
    } else {
      reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
    }
  }];
}

RCT_EXPORT_METHOD(queryLocalFileCache
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim queryLocalFileCacheWithConfig:configObj
                            callback:^(ZIMFileCacheInfo *_Nonnull info,
                                       ZIMError *_Nonnull errorInfo) {
                              if (errorInfo.code == 0) {
                                resolve(@{@"totalFileSize" : @(info.totalFileSize)});
                              } else {
                                reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                       errorInfo.message, nil);
                              }
                            }];
}

RCT_EXPORT_METHOD(clearLocalFileCache
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim clearLocalFileCacheWithConfig:configObj
                            callback:^(ZIMError *_Nonnull errorInfo) {
                              if (errorInfo.code == 0) {
                                resolve(nil);
                              } else {
                                reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                       errorInfo.message, nil);
                              }
                            }];
}

RCT_EXPORT_METHOD(queryConversation
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim queryConversation:conversationID
        conversationType:(ZIMConversationType)conversationType
                callback:^(ZIMConversation *_Nonnull conversation, ZIMError *_Nonnull errorInfo) {
                  if (errorInfo.code == 0) {
                    resolve(@{@"conversation" : [self convertConversationFromOCToJs:conversation]});
                  } else {
                    reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                           nil);
                  }
                }];
}

RCT_EXPORT_METHOD(queryConversationList
                  : (NSString *)handle config
                  : (NSDictionary *)config option
                  : (NSDictionary *)option resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  ZIMConversationFilterOption *optionObj = [[ZIMConversationFilterOption alloc] init];
  optionObj.isOnlyUnreadConversation = [RCTConvert BOOL:option[@"isOnlyUnreadConversation"]];
  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 *> *_Nonnull conversationList,
                                        ZIMError *_Nonnull errorInfo) {
                               if (errorInfo.code == 0) {
                                 NSMutableArray<NSDictionary *> *convArray = [NSMutableArray array];
                                 for (ZIMConversation *conversation in conversationList) {
                                   if (conversation) {
                                     [convArray
                                         addObject:[self
                                                       convertConversationFromOCToJs:conversation]];
                                   }
                                 }

                                 resolve(@{@"conversationList" : convArray});
                               } else {
                                 reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                        errorInfo.message, nil);
                               }
                             }];
}

RCT_EXPORT_METHOD(queryConversationPinnedList
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim
      queryConversationPinnedListWithConfig:configObj
                                   callback:^(NSArray<ZIMConversation *> *_Nonnull conversationList,
                                              ZIMError *_Nonnull errorInfo) {
                                     if (errorInfo.code == 0) {
                                       NSMutableArray<NSDictionary *> *convArray =
                                           [NSMutableArray array];
                                       for (ZIMConversation *conversation in conversationList) {
                                         if (conversation) {
                                           [convArray addObject:[self convertConversationFromOCToJs:
                                                                          conversation]];
                                         }
                                       }

                                       resolve(@{@"conversationList" : convArray});
                                     } else {
                                       reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                              errorInfo.message, nil);
                                     }
                                   }];
}

RCT_EXPORT_METHOD(queryConversationTotalUnreadMessageCount
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull errorInfo) {
                                                   if (errorInfo.code == 0) {
                                                     resolve(@{
                                                       @"unreadMessageCount" : @(unreadMessageCount)
                                                     });
                                                   } else {
                                                     reject(
                                                         [NSString stringWithFormat:@"%lu",
                                                                                    errorInfo.code],
                                                         errorInfo.message, nil);
                                                   }
                                                 }];
}

RCT_EXPORT_METHOD(deleteConversation
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim deleteConversation:conversationID
         conversationType:(ZIMConversationType)conversationType
                   config:configObj
                 callback:^(NSString *_Nonnull conversationID, ZIMConversationType conversationType,
                            ZIMError *_Nonnull errorInfo) {
                   if (errorInfo.code == 0) {
                     resolve(@{
                       @"conversationID" : conversationID,
                       @"conversationType" : @(conversationType)
                     });
                   } else {
                     reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                            nil);
                   }
                 }];
}

RCT_EXPORT_METHOD(deleteAllConversations
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim deleteAllConversationsWithConfig:configObj
                               callback:^(ZIMError *_Nonnull errorInfo) {
                                 if (errorInfo.code == 0) {
                                   resolve(nil);
                                 } else {
                                   reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                          errorInfo.message, 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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim setConversationNotificationStatus:(ZIMConversationNotificationStatus)status
                          conversationID:conversationID
                        conversationType:(ZIMConversationType)conversationType
                                callback:^(NSString *_Nonnull conversationID,
                                           ZIMConversationType conversationType,
                                           ZIMError *_Nonnull errorInfo) {
                                  if (errorInfo.code == 0) {
                                    resolve(@{
                                      @"conversationID" : conversationID,
                                      @"conversationType" : @(conversationType)
                                    });
                                  } else {
                                    reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                           errorInfo.message, nil);
                                  }
                                }];
}

RCT_EXPORT_METHOD(setConversationDraft
                  : (NSString *)handle draft
                  : (NSString *)draft conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim setConversationDraft:draft
             conversationID:conversationID
           conversationType:(ZIMConversationType)conversationType
                   callback:^(NSString *_Nonnull conversationID,
                              ZIMConversationType conversationType, ZIMError *_Nonnull errorInfo) {
                     if (errorInfo.code == 0) {
                       resolve(@{
                         @"conversationID" : conversationID,
                         @"conversationType" : @(conversationType)
                       });
                     } else {
                       reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                              nil);
                     }
                   }];
}

RCT_EXPORT_METHOD(updateConversationPinnedState
                  : (NSString *)handle isPinned
                  : (BOOL)isPinned conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim updateConversationPinnedState:isPinned
                      conversationID:conversationID
                    conversationType:(ZIMConversationType)conversationType
                            callback:^(NSString *_Nonnull conversationID,
                                       ZIMConversationType conversationType,
                                       ZIMError *_Nonnull errorInfo) {
                              if (errorInfo.code == 0) {
                                resolve(@{
                                  @"conversationID" : conversationID,
                                  @"conversationType" : @(conversationType)
                                });
                              } else {
                                reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                       errorInfo.message, nil);
                              }
                            }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *> *_Nonnull failedConversationInfos,
                             ZIMError *_Nonnull errorInfo) {
                    if (errorInfo.code == 0) {
                      NSMutableArray<NSDictionary *> *failedArray = [NSMutableArray array];
                      for (ZIMConversationBaseInfo *info in failedConversationInfos) {
                        [failedArray addObject:@{
                          @"conversationID" : info.conversationID,
                          @"conversationType" : @(info.conversationType)
                        }];
                      }
                      resolve(@{@"failedConversationInfos" : failedArray});
                    } else {
                      reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                             nil);
                    }
                  }];
}

RCT_EXPORT_METHOD(clearConversationUnreadMessageCount
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim clearConversationUnreadMessageCount:conversationID
                          conversationType:conversationType
                                  callback:^(NSString *_Nonnull conversationID,
                                             ZIMConversationType conversationType,
                                             ZIMError *_Nonnull errorInfo) {
                                    if (errorInfo.code == 0) {
                                      resolve(@{
                                        @"conversationID" : conversationID,
                                        @"conversationType" : @(conversationType)
                                      });
                                    } else {
                                      reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                             errorInfo.message, nil);
                                    }
                                  }];
}

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

  [zim clearConversationTotalUnreadMessageCount:^(ZIMError *_Nonnull errorInfo) {
    if (errorInfo.code == 0) {
      resolve(nil);
    } else {
      reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
    }
  }];
}

RCT_EXPORT_METHOD(deleteAllConversationMessages
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim deleteAllConversationMessagesWithConfig:configObj
                                      callback:^(ZIMError *_Nonnull errorInfo) {
                                        if (errorInfo.code == 0) {
                                          resolve(nil);
                                        } else {
                                          reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                                 errorInfo.message, 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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC: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.pushConfig = [self mZIMPushConfig:config];

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

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

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

  notification.onMultipleMediaUploadingProgress =
      ^(ZIMMultipleMessage *_Nonnull message, unsigned long long currentFileSize,
        unsigned long long totalFileSize, unsigned int messageInfoIndex,
        unsigned long long currentIndexFileSize, unsigned long long totalIndexFileSize) {
        NSDictionary *messageDic = [self convertMessageFromOCToJs: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 *_Nonnull message, ZIMError *_Nonnull errorInfo) {
                if (errorInfo.code == 0) {
                  NSDictionary *messageDict = [self convertMessageFromOCToJs:message];
                  resolve(@{@"message" : messageDict});
                } else {
                  reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                         nil);
                }
              }];
}

RCT_EXPORT_METHOD(editMessage
                  : (NSString *)handle message
                  : (NSDictionary *)message _handle
                  : (NSString *)_handle resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC:message];

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

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

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

  notification.onMultipleMediaUploadingProgress =
      ^(ZIMMultipleMessage *_Nonnull message, unsigned long long currentFileSize,
        unsigned long long totalFileSize, unsigned int messageInfoIndex,
        unsigned long long currentIndexFileSize, unsigned long long totalIndexFileSize) {
        NSDictionary *messageDic = [self convertMessageFromOCToJs: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 *_Nonnull message, ZIMError *_Nonnull errorInfo) {
            if (errorInfo.code == 0) {
              NSDictionary *messageDict = [self convertMessageFromOCToJs:message];
              resolve(@{@"message" : messageDict});
            } else {
              reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
            }
          }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  NSMutableArray<ZIMMessage *> *messageObjArray = [NSMutableArray array];
  for (NSDictionary *dict in messageList) {
    ZIMMessage *message = [self convertMessageFromJsToOC:dict];
    if (message) {
      [messageObjArray addObject:message];
    }
  }

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

  [zim deleteMessages:messageObjArray
        conversationID:conversationID
      conversationType:(ZIMConversationType)conversationType
                config:configObj
              callback:^(NSString *_Nonnull conversationID, ZIMConversationType conversationType,
                         ZIMError *_Nonnull errorInfo) {
                if (errorInfo.code == 0) {
                  resolve(@{
                    @"conversationID" : conversationID,
                    @"conversationType" : @(conversationType)
                  });
                } else {
                  reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                         nil);
                }
              }];
}

RCT_EXPORT_METHOD(deleteAllMessage
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim deleteAllMessageByConversationID:conversationID
                       conversationType:(ZIMConversationType)conversationType
                                 config:configObj
                               callback:^(NSString *_Nonnull conversationID,
                                          ZIMConversationType conversationType,
                                          ZIMError *_Nonnull errorInfo) {
                                 if (errorInfo.code == 0) {
                                   resolve(@{
                                     @"conversationID" : conversationID,
                                     @"conversationType" : @(conversationType)
                                   });
                                 } else {
                                   reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                          errorInfo.message, nil);
                                 }
                               }];
}

RCT_EXPORT_METHOD(queryHistoryMessage
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim queryHistoryMessageByConversationID:conversationID
                          conversationType:(ZIMConversationType)conversationType
                                    config:configObj
                                  callback:^(NSString *_Nonnull conversationID,
                                             ZIMConversationType conversationType,
                                             NSArray<ZIMMessage *> *_Nonnull messageList,
                                             ZIMError *_Nonnull errorInfo) {
                                    if (errorInfo.code == 0) {
                                      resolve(@{
                                        @"conversationID" : conversationID,
                                        @"conversationType" : @(conversationType),
                                        @"messageList" : [self mZIMMessageList:messageList]
                                      });
                                    } else {
                                      reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                             errorInfo.message, nil);
                                    }
                                  }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC: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 *_Nonnull message, unsigned long long currentFileSize,
                 unsigned long long totalFileSize) {
        NSDictionary *messageDic = [self convertMessageFromOCToJs:message];
        [self sendEventWithName:RN_EVENT(@"messageLoadingProgress")
                           body:@{
                             @"handle" : _handle,
                             @"data" : @[ messageDic, @(currentFileSize), @(totalFileSize) ]
                           }];
      }
      callback:^(ZIMMessage *_Nonnull message, ZIMError *_Nonnull errorInfo) {
        if (errorInfo.code == 0) {
          NSDictionary *messageDic = [self convertMessageFromOCToJs:message];
          resolve(@{@"message" : messageDic});
        } else {
          reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
        }
      }];
}

RCT_EXPORT_METHOD(importLocalMessages
                  : (NSString *)handle folderPath
                  : (NSString *)folderPath config
                  : (NSDictionary *)config _handle
                  : (NSString *)_handle resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim importLocalMessagesFromFolderPath:folderPath
      config:configObj
      progress:^(unsigned long long currentMessageCount, unsigned long long totalMessageCount) {
        [self sendEventWithName:RN_EVENT(@"messagePortProgress")
                           body:@{
                             @"handle" : _handle,
                             @"data" : @[ @(currentMessageCount), @(totalMessageCount) ]
                           }];
      }
      callback:^(ZIMError *_Nonnull errorInfo) {
        if (errorInfo.code == 0) {
          resolve(nil);
        } else {
          reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
        }
      }];
}

RCT_EXPORT_METHOD(exportLocalMessages
                  : (NSString *)handle folderPath
                  : (NSString *)folderPath config
                  : (NSDictionary *)config _handle
                  : (NSString *)_handle resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim exportLocalMessagesToFolderPath:folderPath
      config:configObj
      progress:^(unsigned long long currentMessageCount, unsigned long long totalMessageCount) {
        [self sendEventWithName:RN_EVENT(@"messagePortProgress")
                           body:@{
                             @"handle" : _handle,
                             @"data" : @[ @(currentMessageCount), @(totalMessageCount) ]
                           }];
      }
      callback:^(ZIMError *_Nonnull errorInfo) {
        if (errorInfo.code == 0) {
          resolve(nil);
        } else {
          reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
        }
      }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC:messageDict];

  [zim insertMessageToLocalDB:messageObj
               conversationID:conversationID
             conversationType:conversationType
                 senderUserID:senderUserID
                     callback:^(ZIMMessage *_Nonnull message, ZIMError *_Nonnull errorInfo) {
                       if (errorInfo.code == 0) {
                         NSDictionary *messageDic = [self convertMessageFromOCToJs:message];
                         resolve(@{@"message" : messageDic});
                       } else {
                         reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                errorInfo.message, nil);
                       }
                     }];
}

RCT_EXPORT_METHOD(updateMessageLocalExtendedData
                  : (NSString *)handle localExtendedData
                  : (NSString *)localExtendedData message
                  : (NSDictionary *)messageDict resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC:messageDict];

  [zim
      updateMessageLocalExtendedData:localExtendedData
                             message:messageObj
                            callback:^(ZIMMessage *_Nonnull message, ZIMError *_Nonnull errorInfo) {
                              if (errorInfo.code == 0) {
                                NSDictionary *messageDic = [self convertMessageFromOCToJs:message];
                                resolve(@{@"message" : messageDic});
                              } else {
                                reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                       errorInfo.message, nil);
                              }
                            }];
}

RCT_EXPORT_METHOD(sendConversationMessageReceiptRead
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim sendConversationMessageReceiptRead:conversationID
                         conversationType:conversationType
                                 callback:^(NSString *_Nonnull conversationID,
                                            ZIMConversationType conversationType,
                                            ZIMError *_Nonnull errorInfo) {
                                   if (errorInfo.code == 0) {
                                     resolve(@{
                                       @"conversationID" : conversationID,
                                       @"conversationType" : @(conversationType)
                                     });
                                   } else {
                                     reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                            errorInfo.message, nil);
                                   }
                                 }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  NSMutableArray<ZIMMessage *> *messageObjArray = [NSMutableArray array];
  for (NSDictionary *dict in messageList) {
    ZIMMessage *message = [self convertMessageFromJsToOC:dict];
    if (message) {
      [messageObjArray addObject:message];
    }
  }

  [zim sendMessageReceiptsRead:messageObjArray
                conversationID:conversationID
              conversationType:conversationType
                      callback:^(NSString *_Nonnull conversationID,
                                 ZIMConversationType conversationType,
                                 NSArray<NSNumber *> *_Nonnull errorMessageIDs,
                                 ZIMError *_Nonnull errorInfo) {
                        if (errorInfo.code == 0) {
                          resolve(@{
                            @"conversationID" : conversationID,
                            @"conversationType" : @(conversationType),
                            @"errorMessageIDs" : [self convertNumberToStringList:errorMessageIDs]
                          });
                        } else {
                          reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                 errorInfo.message, nil);
                        }
                      }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  NSMutableArray<ZIMMessage *> *messageObjArray = [NSMutableArray array];
  for (NSDictionary *dict in messageList) {
    ZIMMessage *message = [self convertMessageFromJsToOC:dict];
    if (message) {
      [messageObjArray addObject:message];
    }
  }

  [zim queryMessageReceiptsInfoByMessageList:messageObjArray
                              conversationID:conversationID
                            conversationType:conversationType
                                    callback:^(NSArray<ZIMMessageReceiptInfo *> *_Nonnull infos,
                                               NSArray<NSNumber *> *_Nonnull errorMessageIDs,
                                               ZIMError *_Nonnull errorInfo) {
                                      if (errorInfo.code == 0) {
                                        resolve(@{
                                          @"infos" : [self mZIMMessageReceiptInfoList:infos],
                                          @"errorMessageIDs" :
                                              [self convertNumberToStringList:errorMessageIDs]
                                        });
                                      } else {
                                        reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                               errorInfo.message, nil);
                                      }
                                    }];
}

RCT_EXPORT_METHOD(queryGroupMessageReceiptMemberList
                  : (NSString *)handle message
                  : (NSDictionary *)message groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config read
                  : (BOOL)read resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC:message];

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

  ZIMGroupMessageReceiptMemberListQueriedCallback callback =
      ^(NSString *groupID, NSArray<ZIMGroupMemberInfo *> *_Nonnull userList, unsigned int nextFlag,
        ZIMError *_Nonnull errorInfo) {
        if (errorInfo.code == 0) {
          resolve(@{
            @"groupID" : groupID,
            @"nextFlag" : @(nextFlag),
            @"userList" : [self mZIMGroupMemberInfoList:userList]
          });
        } else {
          reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
        }
      };

  if (read) {
    [zim queryGroupMessageReceiptReadMemberListByMessage:messageObj
                                                 groupID:groupID
                                                  config:configObj
                                                callback:callback];
  } else {
    [zim queryGroupMessageReceiptUnreadMemberListByMessage:messageObj
                                                   groupID:groupID
                                                    config:configObj
                                                  callback:callback];
  }
}

RCT_EXPORT_METHOD(revokeMessage
                  : (NSString *)handle message
                  : (NSDictionary *)message config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC:message];

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

  [zim revokeMessage:messageObj
              config:configObj
            callback:^(ZIMMessage *_Nonnull message, ZIMError *_Nonnull errorInfo) {
              if (errorInfo.code == 0) {
                resolve(@{@"message" : [self convertMessageFromOCToJs:message]});
              } else {
                reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
              }
            }];
}

RCT_EXPORT_METHOD(queryCombineMessageDetail
                  : (NSString *)handle message
                  : (NSDictionary *)message resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC:message];

  [zim queryCombineMessageDetailByMessage:(ZIMCombineMessage *)messageObj
                                 callback:^(ZIMCombineMessage *_Nonnull message,
                                            ZIMError *_Nonnull errorInfo) {
                                   if (errorInfo.code == 0) {
                                     resolve(
                                         @{@"message" : [self convertMessageFromOCToJs:message]});
                                   } else {
                                     reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                            errorInfo.message, nil);
                                   }
                                 }];
}

RCT_EXPORT_METHOD(addMessageReaction
                  : (NSString *)handle reactionType
                  : (NSString *)reactionType message
                  : (NSDictionary *)message resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC:message];

  [zim addMessageReaction:reactionType
                  message:messageObj
                 callback:^(ZIMMessageReaction *_Nonnull reaction, ZIMError *_Nonnull errorInfo) {
                   if (errorInfo.code == 0) {
                     resolve(@{@"reaction" : [self convertMessageReactionFromOCToJs:reaction]});
                   } else {
                     reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                            nil);
                   }
                 }];
}

RCT_EXPORT_METHOD(deleteMessageReaction
                  : (NSString *)handle reactionType
                  : (NSString *)reactionType message
                  : (NSDictionary *)message resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC:message];

  [zim
      deleteMessageReaction:reactionType
                    message:messageObj
                   callback:^(ZIMMessageReaction *_Nonnull reaction, ZIMError *_Nonnull errorInfo) {
                     if (errorInfo.code == 0) {
                       resolve(@{@"reaction" : [self convertMessageReactionFromOCToJs:reaction]});
                     } else {
                       reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                              nil);
                     }
                   }];
}

RCT_EXPORT_METHOD(queryMessageReactionUserList
                  : (NSString *)handle message
                  : (NSDictionary *)message config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC: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 *_Nonnull message,
                                               NSArray<ZIMMessageReactionUserInfo *> *userList,
                                               NSString *reactionType, long long nextFlag,
                                               int totalCount, ZIMError *_Nonnull errorInfo) {
                                      if (errorInfo.code == 0) {
                                        resolve(@{
                                          @"reactionType" : reactionType,
                                          @"nextFlag" : @(nextFlag),
                                          @"totalCount" : @(totalCount),
                                          @"userList" :
                                              [self mZIMMessageReactionUserInfoList:userList],
                                          @"message" : [self convertMessageFromOCToJs:message]
                                        });
                                      } else {
                                        reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                               errorInfo.message, nil);
                                      }
                                    }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC:message];
  ZIMMessage *originMessageObj = [self convertMessageFromJsToOC: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.pushConfig = [self mZIMPushConfig:config];

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

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

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

  notification.onMultipleMediaUploadingProgress =
      ^(ZIMMultipleMessage *_Nonnull message, unsigned long long currentFileSize,
        unsigned long long totalFileSize, unsigned int messageInfoIndex,
        unsigned long long currentIndexFileSize, unsigned long long totalIndexFileSize) {
        NSDictionary *messageDic = [self convertMessageFromOCToJs: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 *_Nonnull message, ZIMError *_Nonnull errorInfo) {
                 if (errorInfo.code == 0) {
                   NSDictionary *messageDict = [self convertMessageFromOCToJs:message];
                   resolve(@{@"message" : messageDict});
                 } else {
                   reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                          nil);
                 }
               }];
}

RCT_EXPORT_METHOD(queryMessageRepliedList
                  : (NSString *)handle message
                  : (NSDictionary *)message config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMMessage *messageObj = [self convertMessageFromJsToOC: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 *> *_Nonnull messageList,
                                          long long nextFlag,
                                          ZIMMessageRootRepliedInfo *_Nonnull rootRepliedInfo,
                                          ZIMError *_Nonnull errorInfo) {
                                 if (errorInfo.code == 0) {
                                   NSMutableDictionary *infoMap = [NSMutableDictionary dictionary];
                                   infoMap[@"state"] = @(rootRepliedInfo.state);
                                   infoMap[@"repliedCount"] = @(rootRepliedInfo.repliedCount);
                                   infoMap[@"senderUserID"] = rootRepliedInfo.senderUserID;
                                   infoMap[@"sentTime"] = @(rootRepliedInfo.sentTime);

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

                                   resolve(@{
                                     @"nextFlag" : @(nextFlag),
                                     @"rootRepliedInfo" : infoMap,
                                     @"messageList" : [self mZIMMessageList:messageList]
                                   });
                                 } else {
                                   reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                          errorInfo.message, nil);
                                 }
                               }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim queryMessagesByMessageSeqs:messageSeqs
                   conversationID:conversationID
                 conversationType:conversationType
                         callback:^(NSString *_Nonnull conversationID,
                                    ZIMConversationType conversationType,
                                    NSArray<ZIMMessage *> *_Nonnull messageList,
                                    ZIMError *_Nonnull errorInfo) {
                           if (errorInfo.code == 0) {
                             resolve(@{
                               @"conversationID" : conversationID,
                               @"conversationType" : @(conversationType),
                               @"messageList" : [self mZIMMessageList:messageList]
                             });
                           } else {
                             reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                    errorInfo.message, nil);
                           }
                         }];
}

RCT_EXPORT_METHOD(createRoom
                  : (NSString *)handle roomInfo
                  : (NSDictionary *)roomInfo config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMRoomCreatedCallback callback =
      ^(ZIMRoomFullInfo *_Nonnull roomInfo, ZIMError *_Nonnull errorInfo) {
        if (errorInfo.code == 0) {
          resolve(@{
            @"roomInfo" : @{
              @"baseInfo" :
                  @{@"roomID" : roomInfo.baseInfo.roomID, @"roomName" : roomInfo.baseInfo.roomName}
            }
          });
        } else {
          reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
        }
      };

  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 *roomArrtsDict = [RCTConvert NSDictionary:config[@"roomAttributes"]];
    if (!roomArrtsDict || [roomArrtsDict isEqual:[NSNull null]]) {
      configObj.roomAttributes = nil;
    } else {
      configObj.roomAttributes = roomArrtsDict;
    }
  }

  if (!config || [config isEqual:[NSNull null]]) {
    [zim createRoom:roomInfoObj callback:callback];
  } else {
    [zim createRoom:roomInfoObj config:configObj callback:callback];
  }
}

RCT_EXPORT_METHOD(enterRoom
                  : (NSString *)handle roomInfo
                  : (NSDictionary *)roomInfo config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *roomArrtsDict = [RCTConvert NSDictionary:config[@"roomAttributes"]];
    if (!roomArrtsDict || [roomArrtsDict isEqual:[NSNull null]]) {
      configObj.roomAttributes = nil;
    } else {
      configObj.roomAttributes = roomArrtsDict;
    }
  }

  [zim
      enterRoom:roomInfoObj
         config:configObj
       callback:^(ZIMRoomFullInfo *_Nonnull roomInfo, ZIMError *_Nonnull errorInfo) {
         if (errorInfo.code == 0) {
           resolve(@{
             @"roomInfo" : @{
               @"baseInfo" :
                   @{@"roomID" : roomInfo.baseInfo.roomID, @"roomName" : roomInfo.baseInfo.roomName}
             }
           });
         } else {
           reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
         }
       }];
}

RCT_EXPORT_METHOD(joinRoom
                  : (NSString *)handle roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim joinRoom:roomID
       callback:^(ZIMRoomFullInfo *_Nonnull roomInfo, ZIMError *_Nonnull errorInfo) {
         if (errorInfo.code == 0) {
           resolve(@{
             @"roomInfo" : @{
               @"baseInfo" :
                   @{@"roomID" : roomInfo.baseInfo.roomID, @"roomName" : roomInfo.baseInfo.roomName}
             }
           });
         } else {
           reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
         }
       }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *roomArrtsDict = [RCTConvert NSDictionary:config[@"roomAttributes"]];
    if (!roomArrtsDict || [roomArrtsDict isEqual:[NSNull null]]) {
      configObj.roomAttributes = nil;
    } else {
      configObj.roomAttributes = roomArrtsDict;
    }
  }

  [zim switchRoomFromRoomID:fromRoomID
                      toRoomInfo:roomInfoObj
      isCreateWhenRoomNotExisted:isCreateWhenRoomNotExisted
                          config:configObj
                        callback:^(ZIMRoomFullInfo *_Nonnull roomInfo,
                                   ZIMError *_Nonnull errorInfo) {
                          if (errorInfo.code == 0) {
                            resolve(@{
                              @"roomInfo" : @{
                                @"baseInfo" : @{
                                  @"roomID" : roomInfo.baseInfo.roomID,
                                  @"roomName" : roomInfo.baseInfo.roomName
                                }
                              }
                            });
                          } else {
                            reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                   errorInfo.message, nil);
                          }
                        }];
}

RCT_EXPORT_METHOD(leaveRoom
                  : (NSString *)handle roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim leaveRoom:roomID
        callback:^(NSString *_Nonnull roomID, ZIMError *_Nonnull errorInfo) {
          if (errorInfo.code == 0) {
            resolve(@{@"roomID" : roomID});
          } else {
            reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
          }
        }];
}

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

  [zim leaveAllRoom:^(NSArray<NSString *> *_Nonnull roomIDs, ZIMError *_Nonnull errorInfo) {
    if (errorInfo.code == 0) {
      resolve(@{@"roomIDs" : roomIDs});
    } else {
      reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
    }
  }];
}

RCT_EXPORT_METHOD(queryRoomMemberList
                  : (NSString *)handle roomID
                  : (NSString *)roomID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull roomID,
                                     NSArray<ZIMUserInfo *> *_Nonnull memberList,
                                     NSString *_Nonnull nextFlag, ZIMError *_Nonnull errorInfo) {
                            if (errorInfo.code == 0) {
                              resolve(@{
                                @"roomID" : roomID,
                                @"memberList" : [self mZIMUserInfoList:memberList],
                                @"nextFlag" : nextFlag
                              });
                            } else {
                              reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                     errorInfo.message, nil);
                            }
                          }];
}

RCT_EXPORT_METHOD(queryRoomMembers
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim queryRoomMembersByUserIDs:userIDs
                          roomID:roomID
                        callback:^(NSString *_Nonnull roomID,
                                   NSArray<ZIMRoomMemberInfo *> *_Nonnull memberList,
                                   NSArray<ZIMErrorUserInfo *> *_Nonnull errorUserList,
                                   ZIMError *_Nonnull errorInfo) {
                          if (errorInfo.code == 0) {
                            resolve(@{
                              @"roomID" : roomID,
                              @"memberList" : [self mZIMUserInfoList:memberList],
                              @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                            });
                          } else {
                            reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                   errorInfo.message, nil);
                          }
                        }];
}

RCT_EXPORT_METHOD(queryRoomOnlineMemberCount
                  : (NSString *)handle roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim queryRoomOnlineMemberCountByRoomID:roomID
                                 callback:^(NSString *_Nonnull roomID, unsigned int count,
                                            ZIMError *_Nonnull errorInfo) {
                                   if (errorInfo.code == 0) {
                                     resolve(@{@"roomID" : roomID, @"count" : @(count)});
                                   } else {
                                     reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                            errorInfo.message, nil);
                                   }
                                 }];
}

RCT_EXPORT_METHOD(queryRoomAllAttributes
                  : (NSString *)handle roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim queryRoomAllAttributesByRoomID:roomID
                             callback:^(
                                 NSString *_Nonnull roomID,
                                 NSDictionary<NSString *, NSString *> *_Nonnull roomAttributes,
                                 ZIMError *_Nonnull errorInfo) {
                               if (errorInfo.code == 0) {
                                 resolve(@{@"roomID" : roomID, @"roomAttributes" : roomAttributes});
                               } else {
                                 reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                        errorInfo.message, nil);
                               }
                             }];
}

RCT_EXPORT_METHOD(setRoomAttributes
                  : (NSString *)handle roomAttributes
                  : (NSDictionary *)roomAttributes roomID
                  : (NSString *)roomID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull roomID, NSArray<NSString *> *_Nonnull errorKeys,
                           ZIMError *_Nonnull errorInfo) {
                  if (errorInfo.code == 0) {
                    resolve(@{@"roomID" : roomID, @"errorKeys" : errorKeys});
                  } else {
                    reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                           nil);
                  }
                }];
}

RCT_EXPORT_METHOD(deleteRoomAttributes
                  : (NSString *)handle keys
                  : (NSArray *)keys roomID
                  : (NSString *)roomID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim deleteRoomAttributesByKeys:keys
                           roomID:roomID
                           config:configObj
                         callback:^(NSString *_Nonnull roomID,
                                    NSArray<NSString *> *_Nonnull errorKeys,
                                    ZIMError *_Nonnull errorInfo) {
                           if (errorInfo.code == 0) {
                             resolve(@{@"roomID" : roomID, @"errorKeys" : errorKeys});
                           } else {
                             reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                    errorInfo.message, nil);
                           }
                         }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim endRoomAttributesBatchOperationWithRoomID:roomID
                                        callback:^(NSString *_Nonnull roomID,
                                                   ZIMError *_Nonnull errorInfo) {
                                          if (errorInfo.code == 0) {
                                            resolve(@{@"roomID" : roomID});
                                          } else {
                                            reject(
                                                [NSString stringWithFormat:@"%lu", errorInfo.code],
                                                errorInfo.message, nil);
                                          }
                                        }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim setRoomMembersAttributes:attributes
                        userIDs:userIDs
                         roomID:roomID
                         config:configObj
                       callback:^(NSString *_Nonnull roomID,
                                  NSArray<ZIMRoomMemberAttributesOperatedInfo *> *_Nonnull infos,
                                  NSArray<NSString *> *_Nonnull errorUserList,
                                  ZIMError *_Nonnull errorInfo) {
                         if (errorInfo.code == 0) {
                           resolve(@{
                             @"roomID" : roomID,
                             @"infos" : [self mZIMRoomMemberAttributesOperatedInfoList:infos],
                             @"errorUserList" : errorUserList
                           });
                         } else {
                           reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                  errorInfo.message, nil);
                         }
                       }];
}

RCT_EXPORT_METHOD(queryRoomMembersAttributes
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs roomID
                  : (NSString *)roomID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim queryRoomMembersAttributesByUserIDs:userIDs
                                    roomID:roomID
                                  callback:^(NSString *_Nonnull roomID,
                                             NSArray<ZIMRoomMemberAttributesInfo *> *_Nonnull infos,
                                             ZIMError *_Nonnull errorInfo) {
                                    if (errorInfo.code == 0) {
                                      resolve(@{
                                        @"roomID" : roomID,
                                        @"infos" : [self mZIMRoomMemberAttributesInfoList:infos]
                                      });
                                    } else {
                                      reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                             errorInfo.message, nil);
                                    }
                                  }];
}

RCT_EXPORT_METHOD(queryRoomMemberAttributesList
                  : (NSString *)handle roomID
                  : (NSString *)roomID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull roomID,
                                        NSArray<ZIMRoomMemberAttributesInfo *> *_Nonnull infos,
                                        NSString *_Nonnull nextFlag, ZIMError *_Nonnull errorInfo) {
                                      if (errorInfo.code == 0) {
                                        resolve(@{
                                          @"roomID" : roomID,
                                          @"infos" : [self mZIMRoomMemberAttributesInfoList:infos],
                                          @"nextFlag" : nextFlag
                                        });
                                      } else {
                                        reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                               errorInfo.message, nil);
                                      }
                                    }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMGroupCreatedCallback callback =
      ^(ZIMGroupFullInfo *_Nonnull groupInfo, NSArray<ZIMGroupMemberInfo *> *_Nonnull userList,
        NSArray<ZIMErrorUserInfo *> *_Nonnull errorUserList, ZIMError *_Nonnull errorInfo) {
        if (errorInfo.code == 0) {
          resolve(@{
            @"groupInfo" : [self mZIMGroupFullInfo:groupInfo],
            @"userList" : [self mZIMGroupMemberInfoList:userList],
            @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
          });
        } else {
          reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
        }
      };

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

  ZIMGroupAdvancedConfig *configObj = [[ZIMGroupAdvancedConfig alloc] init];
  if (config && ![config isEqual:[NSNull null]]) {
    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 *groupArrtsDict = [RCTConvert NSDictionary:config[@"groupAttributes"]];
    if (!groupArrtsDict || [groupArrtsDict isEqual:[NSNull null]]) {
      configObj.groupAttributes = nil;
    } else {
      configObj.groupAttributes = groupArrtsDict;
    }
  }

  if (!config || [config isEqual:[NSNull null]]) {
    [zim createGroup:groupInfoObj userIDs:userIDs callback:callback];
  } else {
    [zim createGroup:groupInfoObj userIDs:userIDs config:configObj callback:callback];
  }
}

RCT_EXPORT_METHOD(joinGroup
                  : (NSString *)handle groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim joinGroup:groupID
        callback:^(ZIMGroupFullInfo *_Nonnull groupInfo, ZIMError *_Nonnull errorInfo) {
          if (errorInfo.code == 0) {
            resolve(@{@"groupInfo" : [self mZIMGroupFullInfo:groupInfo]});
          } else {
            reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
          }
        }];
}

RCT_EXPORT_METHOD(leaveGroup
                  : (NSString *)handle groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim leaveGroup:groupID
         callback:^(NSString *_Nonnull groupID, ZIMError *_Nonnull errorInfo) {
           if (errorInfo.code == 0) {
             resolve(@{@"groupID" : groupID});
           } else {
             reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
           }
         }];
}

RCT_EXPORT_METHOD(dismissGroup
                  : (NSString *)handle groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim dismissGroup:groupID
           callback:^(NSString *_Nonnull groupID, ZIMError *_Nonnull errorInfo) {
             if (errorInfo.code == 0) {
               resolve(@{@"groupID" : groupID});
             } else {
               reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
             }
           }];
}

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

  [zim queryGroupList:^(NSArray<ZIMGroup *> *_Nonnull groupList, ZIMError *_Nonnull errorInfo) {
    if (errorInfo.code == 0) {
      NSMutableArray *groupArray = [NSMutableArray array];
      for (ZIMGroup *group in groupList) {
        [groupArray addObject:@{
          @"baseInfo" : @{
            @"groupID" : group.baseInfo.groupID,
            @"groupName" : group.baseInfo.groupName,
            @"groupAvatarUrl" : group.baseInfo.groupAvatarUrl
          },
          @"notificationStatus" : @(group.notificationStatus)
        }];
      }
      resolve(@{@"groupList" : groupArray});
    } else {
      reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
    }
  }];
}

RCT_EXPORT_METHOD(updateGroupNotice
                  : (NSString *)handle groupNotice
                  : (NSString *)groupNotice groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim updateGroupNotice:groupNotice
                 groupID:groupID
                callback:^(NSString *_Nonnull groupID, NSString *_Nonnull groupNotice,
                           ZIMError *_Nonnull errorInfo) {
                  if (errorInfo.code == 0) {
                    resolve(@{@"groupID" : groupID, @"groupNotice" : groupNotice});
                  } else {
                    reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                           nil);
                  }
                }];
}

RCT_EXPORT_METHOD(updateGroupName
                  : (NSString *)handle groupName
                  : (NSString *)groupName groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim
      updateGroupName:groupName
              groupID:groupID
             callback:^(NSString *_Nonnull groupID, NSString *_Nonnull groupName,
                        ZIMError *_Nonnull errorInfo) {
               if (errorInfo.code == 0) {
                 resolve(@{@"groupID" : groupID, @"groupName" : groupName});
               } else {
                 reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
               }
             }];
}

RCT_EXPORT_METHOD(updateGroupAvatarUrl
                  : (NSString *)handle groupAvatarUrl
                  : (NSString *)groupAvatarUrl groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim updateGroupAvatarUrl:groupAvatarUrl
                    groupID:groupID
                   callback:^(NSString *_Nonnull groupID, NSString *_Nonnull groupAvatarUrl,
                              ZIMError *_Nonnull errorInfo) {
                     if (errorInfo.code == 0) {
                       resolve(@{@"groupID" : groupID, @"groupAvatarUrl" : groupAvatarUrl});
                     } else {
                       reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                              nil);
                     }
                   }];
}

RCT_EXPORT_METHOD(updateGroupAlias
                  : (NSString *)handle groupAlias
                  : (NSString *)groupAlias groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }
  [zim updateGroupAlias:groupAlias
                groupID:groupID
               callback:^(NSString *_Nonnull groupID, NSString *_Nonnull groupAlias,
                          ZIMError *_Nonnull errorInfo) {
                 if (errorInfo.code == 0) {
                   resolve(@{@"groupID" : groupID, @"groupAlias" : groupAlias});
                 } else {
                   reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                          nil);
                 }
               }];
}

RCT_EXPORT_METHOD(muteGroup
                  : (NSString *)handle isMute
                  : (BOOL)isMute groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull groupID, BOOL isMute, ZIMGroupMuteInfo *muteInfo,
                   ZIMError *_Nonnull errorInfo) {
          if (errorInfo.code == 0) {
            resolve(@{
              @"groupID" : groupID,
              @"isMute" : [NSNumber numberWithBool:isMute],
              @"mutedInfo" : [self mZIMGroupMuteInfo:muteInfo]
            });
          } else {
            reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
          }
        }];
}

RCT_EXPORT_METHOD(queryGroupInfo
                  : (NSString *)handle groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim queryGroupInfoByGroupID:groupID
                      callback:^(ZIMGroupFullInfo *_Nonnull groupInfo,
                                 ZIMError *_Nonnull errorInfo) {
                        if (errorInfo.code == 0) {
                          resolve(@{@"groupInfo" : [self mZIMGroupFullInfo:groupInfo]});
                        } else {
                          reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                 errorInfo.message, nil);
                        }
                      }];
}

RCT_EXPORT_METHOD(queryGroupMemberCount
                  : (NSString *)handle groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim queryGroupMemberCountByGroupID:groupID
                             callback:^(NSString *_Nonnull groupID, unsigned int count,
                                        ZIMError *_Nonnull errorInfo) {
                               if (errorInfo.code == 0) {
                                 resolve(@{@"groupID" : groupID, @"count" : @(count)});
                               } else {
                                 reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                        errorInfo.message, nil);
                               }
                             }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim muteGroupMembers:isMute
                userIDs:userIDs
                groupID:groupID
                 config:configObj
               callback:^(NSString *_Nonnull groupID, BOOL isMute, int duration,
                          NSArray<NSString *> *users, NSArray<ZIMErrorUserInfo *> *errorUserList,
                          ZIMError *_Nonnull errorInfo) {
                 if (errorInfo.code == 0) {
                   resolve(@{
                     @"groupID" : groupID,
                     @"isMute" : [NSNumber numberWithBool:isMute],
                     @"duration" : @(duration),
                     @"mutedUserIDs" : users,
                     @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                   });
                 } else {
                   reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                          nil);
                 }
               }];
}

RCT_EXPORT_METHOD(setGroupAttributes
                  : (NSString *)handle groupAttributes
                  : (NSDictionary *)groupAttributes groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim setGroupAttributes:groupAttributes
                  groupID:groupID
                 callback:^(NSString *_Nonnull groupID, NSArray<NSString *> *_Nonnull errorKeys,
                            ZIMError *_Nonnull errorInfo) {
                   if (errorInfo.code == 0) {
                     resolve(@{@"groupID" : groupID, @"errorKeys" : errorKeys});
                   } else {
                     reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                            nil);
                   }
                 }];
}

RCT_EXPORT_METHOD(deleteGroupAttributes
                  : (NSString *)handle keys
                  : (NSArray<NSString *> *)keys groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim deleteGroupAttributesByKeys:keys
                           groupID:groupID
                          callback:^(NSString *_Nonnull groupID,
                                     NSArray<NSString *> *_Nonnull errorKeys,
                                     ZIMError *_Nonnull errorInfo) {
                            if (errorInfo.code == 0) {
                              resolve(@{@"groupID" : groupID, @"errorKeys" : errorKeys});
                            } else {
                              reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                     errorInfo.message, nil);
                            }
                          }];
}

RCT_EXPORT_METHOD(queryGroupAttributes
                  : (NSString *)handle keys
                  : (NSArray<NSString *> *)keys groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim queryGroupAttributesByKeys:keys
                          groupID:groupID
                         callback:^(NSString *_Nonnull groupID,
                                    NSDictionary<NSString *, NSString *> *_Nonnull groupAttributes,
                                    ZIMError *_Nonnull errorInfo) {
                           if (errorInfo.code == 0) {
                             resolve(@{@"groupID" : groupID, @"groupAttributes" : groupAttributes});
                           } else {
                             reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                    errorInfo.message, nil);
                           }
                         }];
}

RCT_EXPORT_METHOD(queryGroupAllAttributes
                  : (NSString *)handle groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim queryGroupAllAttributesByGroupID:groupID
                               callback:^(
                                   NSString *_Nonnull groupID,
                                   NSDictionary<NSString *, NSString *> *_Nonnull groupAttributes,
                                   ZIMError *_Nonnull errorInfo) {
                                 if (errorInfo.code == 0) {
                                   resolve(@{
                                     @"groupID" : groupID,
                                     @"groupAttributes" : groupAttributes
                                   });
                                 } else {
                                   reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                          errorInfo.message, nil);
                                 }
                               }];
}

RCT_EXPORT_METHOD(setGroupMemberNickname
                  : (NSString *)handle nickname
                  : (NSString *)nickname forUserID
                  : (NSString *)forUserID groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim setGroupMemberNickname:nickname
                    forUserID:forUserID
                      groupID:groupID
                     callback:^(NSString *_Nonnull groupID, NSString *_Nonnull forUserID,
                                NSString *_Nonnull nickname, ZIMError *_Nonnull errorInfo) {
                       if (errorInfo.code == 0) {
                         resolve(@{
                           @"groupID" : groupID,
                           @"forUserID" : forUserID,
                           @"nickname" : nickname
                         });
                       } else {
                         reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                errorInfo.message, nil);
                       }
                     }];
}

RCT_EXPORT_METHOD(setGroupMemberRole
                  : (NSString *)handle role
                  : (int)role forUserID
                  : (NSString *)forUserID groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim setGroupMemberRole:role
                forUserID:forUserID
                  groupID:groupID
                 callback:^(NSString *_Nonnull groupID, NSString *_Nonnull forUserID,
                            ZIMGroupMemberRole role, ZIMError *_Nonnull errorInfo) {
                   if (errorInfo.code == 0) {
                     resolve(@{@"groupID" : groupID, @"forUserID" : forUserID, @"role" : @(role)});
                   } else {
                     reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                            nil);
                   }
                 }];
}

RCT_EXPORT_METHOD(transferGroupOwner
                  : (NSString *)handle toUserID
                  : (NSString *)toUserID groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim transferGroupOwnerToUserID:toUserID
                          groupID:groupID
                         callback:^(NSString *_Nonnull groupID, NSString *_Nonnull toUserID,
                                    ZIMError *_Nonnull errorInfo) {
                           if (errorInfo.code == 0) {
                             resolve(@{@"groupID" : groupID, @"toUserID" : toUserID});
                           } else {
                             reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                    errorInfo.message, nil);
                           }
                         }];
}

RCT_EXPORT_METHOD(queryGroupMemberInfo
                  : (NSString *)handle userID
                  : (NSString *)userID groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim queryGroupMemberInfoByUserID:userID
                            groupID:groupID
                           callback:^(NSString *_Nonnull groupID,
                                      ZIMGroupMemberInfo *_Nonnull userInfo,
                                      ZIMError *_Nonnull errorInfo) {
                             if (errorInfo.code == 0) {
                               resolve(@{
                                 @"groupID" : groupID,
                                 @"userInfo" : [self mZIMGroupMemberInfo:userInfo]
                               });
                             } else {
                               reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                      errorInfo.message, nil);
                             }
                           }];
}

RCT_EXPORT_METHOD(inviteUsersIntoGroup
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim inviteUsersIntoGroup:userIDs
                    groupID:groupID
                   callback:^(NSString *_Nonnull groupID,
                              NSArray<ZIMGroupMemberInfo *> *_Nonnull userList,
                              NSArray<ZIMErrorUserInfo *> *_Nonnull errorUserList,
                              ZIMError *_Nonnull errorInfo) {
                     if (errorInfo.code == 0) {
                       resolve(@{
                         @"groupID" : groupID,
                         @"userList" : [self mZIMGroupMemberInfoList:userList],
                         @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                       });
                     } else {
                       reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                              nil);
                     }
                   }];
}

RCT_EXPORT_METHOD(kickGroupMembers
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim
      kickGroupMembers:userIDs
               groupID:groupID
              callback:^(NSString *_Nonnull groupID, NSArray<NSString *> *_Nonnull kickedUserIDList,
                         NSArray<ZIMErrorUserInfo *> *_Nonnull errorUserList,
                         ZIMError *_Nonnull errorInfo) {
                if (errorInfo.code == 0) {
                  resolve(@{
                    @"groupID" : groupID,
                    @"kickedUserIDs" : kickedUserIDList,
                    @"errorUserList" : [self mZIMErrorUserInfoList:errorUserList]
                  });
                } else {
                  reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                         nil);
                }
              }];
}

RCT_EXPORT_METHOD(queryGroupMemberList
                  : (NSString *)handle groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull groupID,
                                       NSArray<ZIMGroupMemberInfo *> *_Nonnull userList,
                                       unsigned int nextFlag, ZIMError *_Nonnull errorInfo) {
                              if (errorInfo.code == 0) {
                                resolve(@{
                                  @"groupID" : groupID,
                                  @"userList" : [self mZIMGroupMemberInfoList:userList],
                                  @"nextFlag" : @(nextFlag)
                                });
                              } else {
                                reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                       errorInfo.message, nil);
                              }
                            }];
}

RCT_EXPORT_METHOD(queryGroupMemberMutedList
                  : (NSString *)handle groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull groupID, unsigned long long nextFlag,
                                            NSArray<ZIMGroupMemberInfo *> *_Nonnull userList,
                                            ZIMError *_Nonnull errorInfo) {
                                   if (errorInfo.code == 0) {
                                     resolve(@{
                                       @"groupID" : groupID,
                                       @"userList" : [self mZIMGroupMemberInfoList:userList],
                                       @"nextFlag" : @(nextFlag)
                                     });
                                   } else {
                                     reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                            errorInfo.message, nil);
                                   }
                                 }];
}

RCT_EXPORT_METHOD(updateGroupJoinMode
                  : (NSString *)handle mode
                  : (int)mode groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim updateGroupJoinMode:(ZIMGroupJoinMode)mode
                   groupID:groupID
                  callback:^(NSString *_Nonnull groupID, ZIMGroupJoinMode mode,
                             ZIMError *_Nonnull errorInfo) {
                    if (errorInfo.code == 0) {
                      resolve(@{@"groupID" : groupID, @"mode" : @(mode)});
                    } else {
                      reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                             nil);
                    }
                  }];
}

RCT_EXPORT_METHOD(updateGroupInviteMode
                  : (NSString *)handle mode
                  : (int)mode groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim updateGroupInviteMode:(ZIMGroupInviteMode)mode
                     groupID:groupID
                    callback:^(NSString *_Nonnull groupID, ZIMGroupInviteMode mode,
                               ZIMError *_Nonnull errorInfo) {
                      if (errorInfo.code == 0) {
                        resolve(@{@"groupID" : groupID, @"mode" : @(mode)});
                      } else {
                        reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                               errorInfo.message, nil);
                      }
                    }];
}

RCT_EXPORT_METHOD(updateGroupBeInviteMode
                  : (NSString *)handle mode
                  : (int)mode groupID
                  : (NSString *)groupID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim updateGroupBeInviteMode:(ZIMGroupBeInviteMode)mode
                       groupID:groupID
                      callback:^(NSString *_Nonnull groupID, ZIMGroupBeInviteMode mode,
                                 ZIMError *_Nonnull errorInfo) {
                        if (errorInfo.code == 0) {
                          resolve(@{@"groupID" : groupID, @"mode" : @(mode)});
                        } else {
                          reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                 errorInfo.message, nil);
                        }
                      }];
}

RCT_EXPORT_METHOD(sendGroupJoinApplication
                  : (NSString *)handle groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim sendGroupJoinApplicationToGroupID:groupID
                                  config:configObj
                                callback:^(NSString *_Nonnull groupID,
                                           ZIMError *_Nonnull errorInfo) {
                                  if (errorInfo.code == 0) {
                                    resolve(@{@"groupID" : groupID});
                                  } else {
                                    reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                           errorInfo.message, nil);
                                  }
                                }];
}

RCT_EXPORT_METHOD(acceptGroupJoinApplication
                  : (NSString *)handle userID
                  : (NSString *)userID groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim acceptGroupJoinApplicationFromUserID:userID
                                    groupID:groupID
                                     config:configObj
                                   callback:^(NSString *_Nonnull groupID, NSString *_Nonnull userID,
                                              ZIMError *_Nonnull errorInfo) {
                                     if (errorInfo.code == 0) {
                                       resolve(@{@"groupID" : groupID, @"userID" : userID});
                                     } else {
                                       reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                              errorInfo.message, nil);
                                     }
                                   }];
}

RCT_EXPORT_METHOD(rejectGroupJoinApplication
                  : (NSString *)handle userID
                  : (NSString *)userID groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim rejectGroupJoinApplicationFromUserID:userID
                                    groupID:groupID
                                     config:configObj
                                   callback:^(NSString *_Nonnull groupID, NSString *_Nonnull userID,
                                              ZIMError *_Nonnull errorInfo) {
                                     if (errorInfo.code == 0) {
                                       resolve(@{@"groupID" : groupID, @"userID" : userID});
                                     } else {
                                       reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                              errorInfo.message, nil);
                                     }
                                   }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull groupID,
                                              NSArray<ZIMErrorUserInfo *> *_Nonnull errorUserList,
                                              ZIMError *_Nonnull errorInfo) {
                                     if (errorInfo.code == 0) {
                                       resolve(@{
                                         @"groupID" : groupID,
                                         @"errorUserList" :
                                             [self mZIMErrorUserInfoList:errorUserList]
                                       });
                                     } else {
                                       reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                              errorInfo.message, nil);
                                     }
                                   }];
}

RCT_EXPORT_METHOD(acceptGroupInviteApplication
                  : (NSString *)handle userID
                  : (NSString *)userID groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim acceptGroupInviteApplicationFromInviterUserID:userID
                                             groupID:groupID
                                              config:configObj
                                            callback:^(ZIMGroupFullInfo *_Nonnull groupInfo,
                                                       NSString *_Nonnull userID,
                                                       ZIMError *_Nonnull errorInfo) {
                                              if (errorInfo.code == 0) {
                                                resolve(@{
                                                  @"groupInfo" : [self mZIMGroupFullInfo:groupInfo],
                                                  @"inviterUserID" : userID
                                                });
                                              } else {
                                                reject([NSString
                                                           stringWithFormat:@"%lu", errorInfo.code],
                                                       errorInfo.message, nil);
                                              }
                                            }];
}

RCT_EXPORT_METHOD(rejectGroupInviteApplication
                  : (NSString *)handle userID
                  : (NSString *)userID groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim rejectGroupInviteApplicationFromInviterUserID:userID
                                             groupID:groupID
                                              config:configObj
                                            callback:^(NSString *_Nonnull groupID,
                                                       NSString *_Nonnull userID,
                                                       ZIMError *_Nonnull errorInfo) {
                                              if (errorInfo.code == 0) {
                                                resolve(@{
                                                  @"groupID" : groupID,
                                                  @"inviterUserID" : userID
                                                });
                                              } else {
                                                reject([NSString
                                                           stringWithFormat:@"%lu", errorInfo.code],
                                                       errorInfo.message, nil);
                                              }
                                            }];
}

RCT_EXPORT_METHOD(queryGroupApplicationList
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 int nextFlag, ZIMError *_Nonnull errorInfo) {
                                    if (errorInfo.code == 0) {
                                      resolve(@{
                                        @"nextFlag" : @(nextFlag),
                                        @"applicationList" : [self mZIMGroupApplicationList:infos]
                                      });
                                    } else {
                                      reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                             errorInfo.message, nil);
                                    }
                                  }];
}

RCT_EXPORT_METHOD(callInvite
                  : (NSString *)handle invitees
                  : (NSArray<NSString *> *)invitees config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull callID, ZIMCallInvitationSentInfo *_Nonnull info,
                                ZIMError *_Nonnull errorInfo) {
                       if (errorInfo.code == 0) {
                         resolve(@{
                           @"callID" : callID,
                           @"timeout" : @(info.timeout),
                           @"errorUserList" : [self mZIMErrorUserInfoList:info.errorUserList],
                           @"errorInvitees" : [self mZIMCallUserList:info.errorInvitees]
                         });
                       } else {
                         reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                errorInfo.message, nil);
                       }
                     }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull callID,
                                NSArray<NSString *> *_Nonnull errorInvitees,
                                ZIMError *_Nonnull errorInfo) {
                       if (errorInfo.code == 0) {
                         resolve(@{@"callID" : callID, @"errorInvitees" : errorInvitees});
                       } else {
                         reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                errorInfo.message, nil);
                       }
                     }];
}

RCT_EXPORT_METHOD(callAccept
                  : (NSString *)handle callID
                  : (NSString *)callID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim callAcceptWithCallID:callID
                     config:configObj
                   callback:^(NSString *_Nonnull callID, ZIMError *_Nonnull errorInfo) {
                     if (errorInfo.code == 0) {
                       resolve(@{@"callID" : callID});
                     } else {
                       reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                              nil);
                     }
                   }];
}

RCT_EXPORT_METHOD(callReject
                  : (NSString *)handle callID
                  : (NSString *)callID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim callRejectWithCallID:callID
                     config:configObj
                   callback:^(NSString *_Nonnull callID, ZIMError *_Nonnull errorInfo) {
                     if (errorInfo.code == 0) {
                       resolve(@{@"callID" : callID});
                     } else {
                       reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                              nil);
                     }
                   }];
}

RCT_EXPORT_METHOD(callQuit
                  : (NSString *)handle callID
                  : (NSString *)callID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim callQuit:callID
         config:configObj
       callback:^(NSString *_Nonnull callID, ZIMCallQuitSentInfo *_Nonnull info,
                  ZIMError *_Nonnull errorInfo) {
         if (errorInfo.code == 0) {
           resolve(@{
             @"callID" : callID,
             @"createTime" : @(info.createTime),
             @"acceptTime" : @(info.acceptTime),
             @"quitTime" : @(info.quitTime)
           });
         } else {
           reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
         }
       }];
}

RCT_EXPORT_METHOD(callEnd
                  : (NSString *)handle callID
                  : (NSString *)callID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim callEnd:callID
        config:configObj
      callback:^(NSString *_Nonnull callID, ZIMCallEndedSentInfo *_Nonnull info,
                 ZIMError *_Nonnull errorInfo) {
        if (errorInfo.code == 0) {
          resolve(@{
            @"callID" : callID,
            @"createTime" : @(info.createTime),
            @"acceptTime" : @(info.acceptTime),
            @"endTime" : @(info.endTime)
          });
        } else {
          reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
        }
      }];
}

RCT_EXPORT_METHOD(callJoin
                  : (NSString *)handle callID
                  : (NSString *)callID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim callJoin:callID
         config:configObj
       callback:^(NSString *_Nonnull callID, ZIMCallJoinSentInfo *_Nonnull info,
                  ZIMError *_Nonnull errorInfo) {
         if (errorInfo.code == 0) {
           resolve(@{
             @"callID" : callID,
             @"createTime" : @(info.createTime),
             @"joinTime" : @(info.joinTime),
             @"extendedData" : info.extendedData,
             @"callUserList" : [self mZIMCallUserList:info.callUserList]
           });
         } else {
           reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message, nil);
         }
       }];
}

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.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim callingInviteWithInvitees:invitees
                          callID:callID
                          config:configObj
                        callback:^(NSString *_Nonnull callID,
                                   ZIMCallingInvitationSentInfo *_Nonnull info,
                                   ZIMError *_Nonnull errorInfo) {
                          if (errorInfo.code == 0) {
                            resolve(@{
                              @"callID" : callID,
                              @"errorUserList" : [self mZIMErrorUserInfoList:info.errorUserList]
                            });
                          } else {
                            reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                   errorInfo.message, nil);
                          }
                        }];
}

RCT_EXPORT_METHOD(queryCallInvitationList
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *> *_Nonnull callList,
                                           long long nextFlag, ZIMError *_Nonnull errorInfo) {
                                  if (errorInfo.code == 0) {
                                    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});
                                  } else {
                                    reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                           errorInfo.message, nil);
                                  }
                                }];
}

RCT_EXPORT_METHOD(searchConversations
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  ZIMConversationSearchConfig *configObj = [[ZIMConversationSearchConfig alloc] init];
  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 *>
                                                *_Nonnull conversationSearchInfoList,
                                            unsigned int nextFlag, ZIMError *_Nonnull errorInfo) {
                                   if (errorInfo.code == 0) {
                                     NSMutableArray<NSDictionary *> *listArray =
                                         [NSMutableArray array];
                                     for (ZIMConversationSearchInfo
                                              *info in conversationSearchInfoList) {
                                       [listArray addObject:@{
                                         @"conversationID" : info.conversationID,
                                         @"conversationType" : @(info.conversationType),
                                         @"totalMessageCount" : @(info.totalMessageCount),
                                         @"messageList" : [self mZIMMessageList:info.messageList]
                                       }];
                                     }
                                     resolve(@{
                                       @"nextFlag" : @(nextFlag),
                                       @"conversationSearchInfoList" : listArray
                                     });
                                   } else {
                                     reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                            errorInfo.message, nil);
                                   }
                                 }];
}

RCT_EXPORT_METHOD(searchGlobalMessages
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 convertMessageFromJsToOC: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 *> *_Nonnull messageList,
                                             ZIMMessage *_Nullable nextMessage,
                                             ZIMError *_Nonnull errorInfo) {
                                    if (errorInfo.code == 0) {
                                      NSDictionary *msgDict =
                                          [self convertMessageFromOCToJs:nextMessage];
                                      resolve(@{
                                        @"nextMessage" : msgDict == nil ? [NSNull null] : msgDict,
                                        @"messageList" : [self mZIMMessageList:messageList]
                                      });
                                    } else {
                                      reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                             errorInfo.message, nil);
                                    }
                                  }];
}

RCT_EXPORT_METHOD(searchMessages
                  : (NSString *)handle conversationID
                  : (NSString *)conversationID conversationType
                  : (int)conversationType config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 convertMessageFromJsToOC: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 *> *_Nonnull messageList,
                                             ZIMMessage *_Nullable nextMessage,
                                             ZIMError *_Nonnull errorInfo) {
                                    if (errorInfo.code == 0) {
                                      NSDictionary *msgDict =
                                          [self convertMessageFromOCToJs:nextMessage];
                                      resolve(@{
                                        @"conversationID" : conversationID,
                                        @"conversationType" : @(conversationType),
                                        @"nextMessage" : msgDict == nil ? [NSNull null] : msgDict,
                                        @"messageList" : [self mZIMMessageList:messageList]
                                      });
                                    } else {
                                      reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                             errorInfo.message, nil);
                                    }
                                  }];
}

RCT_EXPORT_METHOD(searchGroups
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *> *_Nonnull groupSearchInfoList,
                                     unsigned int nextFlag, ZIMError *_Nonnull errorInfo) {
                            if (errorInfo.code == 0) {
                              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});
                            } else {
                              reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                     errorInfo.message, nil);
                            }
                          }];
}

RCT_EXPORT_METHOD(searchGroupMembers
                  : (NSString *)handle groupID
                  : (NSString *)groupID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *> *_Nonnull userList,
                                          unsigned int nextFlag, ZIMError *_Nonnull errorInfo) {
                                 if (errorInfo.code == 0) {
                                   resolve(@{
                                     @"nextFlag" : @(nextFlag),
                                     @"groupID" : groupID,
                                     @"userList" : [self mZIMGroupMemberInfoList:userList],
                                   });
                                 } else {
                                   reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                          errorInfo.message, nil);
                                 }
                               }];
}

RCT_EXPORT_METHOD(searchFriends
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull errorInfo) {
                             if (errorInfo.code == 0) {
                               resolve(@{
                                 @"nextFlag" : @(nextFlag),
                                 @"friendInfos" : [self mZIMFriendInfoList:userList],
                               });
                             } else {
                               reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                      errorInfo.message, nil);
                             }
                           }];
}

RCT_EXPORT_METHOD(addFriend
                  : (NSString *)handle userID
                  : (NSString *)userID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull errorInfo) {
                  if (errorInfo.code == 0) {
                    resolve(@{@"friendInfo" : [self mZIMFriendInfo:friendInfo]});
                  } else {
                    reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                           nil);
                  }
                }];
}

RCT_EXPORT_METHOD(sendFriendApplication
                  : (NSString *)handle userID
                  : (NSString *)userID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull errorInfo) {
                              if (errorInfo.code == 0) {
                                resolve(@{
                                  @"applicationInfo" : @{
                                    @"type" : @(info.type),
                                    @"state" : @(info.state),
                                    @"wording" : info.wording,
                                    @"createTime" : @(info.createTime),
                                    @"updateTime" : @(info.updateTime),
                                    @"applyUser" : [self mZIMUserInfo:info.applyUser],
                                  }
                                });
                              } else {
                                reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                       errorInfo.message, nil);
                              }
                            }];
}

RCT_EXPORT_METHOD(deleteFriends
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim deleteFriendsByUserIDs:userIDs
                       config:configObj
                     callback:^(NSArray<ZIMErrorUserInfo *> *errorUsers,
                                ZIMError *_Nonnull errorInfo) {
                       if (errorInfo.code == 0) {
                         resolve(@{@"errorUserList" : [self mZIMErrorUserInfoList:errorUsers]});
                       } else {
                         reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                errorInfo.message, nil);
                       }
                     }];
}

RCT_EXPORT_METHOD(checkFriendsRelation
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull errorInfo) {
                              if (errorInfo.code == 0) {
                                NSMutableArray *resultArray = [NSMutableArray array];
                                for (ZIMFriendRelationInfo *info in infos) {
                                  [resultArray addObject:@{
                                    @"userID" : info.userID,
                                    @"type" : @(info.type)
                                  }];
                                }

                                resolve(@{
                                  @"relationInfos" : resultArray,
                                  @"errorUserList" : [self mZIMErrorUserInfoList:errorUsers]
                                });
                              } else {
                                reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                       errorInfo.message, nil);
                              }
                            }];
}

RCT_EXPORT_METHOD(updateFriendAlias
                  : (NSString *)handle friendAlias
                  : (NSString *)friendAlias userID
                  : (NSString *)userID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim updateFriendAlias:friendAlias
                  userID:userID
                callback:^(ZIMFriendInfo *friendInfo, ZIMError *_Nonnull errorInfo) {
                  if (errorInfo.code == 0) {
                    resolve(@{@"friendInfo" : [self mZIMFriendInfo:friendInfo]});
                  } else {
                    reject([NSString stringWithFormat:@"%lu", errorInfo.code], errorInfo.message,
                           nil);
                  }
                }];
}

RCT_EXPORT_METHOD(updateFriendAttributes
                  : (NSString *)handle friendAttributes
                  : (NSDictionary *)friendAttributes userID
                  : (NSString *)userID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim updateFriendAttributes:friendAttributes
                       userID:userID
                     callback:^(ZIMFriendInfo *friendInfo, ZIMError *_Nonnull errorInfo) {
                       if (errorInfo.code == 0) {
                         resolve(@{@"friendInfo" : [self mZIMFriendInfo:friendInfo]});
                       } else {
                         reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                errorInfo.message, nil);
                       }
                     }];
}

RCT_EXPORT_METHOD(acceptFriendApplication
                  : (NSString *)handle userID
                  : (NSString *)userID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull errorInfo) {
                                 if (errorInfo.code == 0) {
                                   resolve(@{@"friendInfo" : [self mZIMFriendInfo:friendInfo]});
                                 } else {
                                   reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                          errorInfo.message, nil);
                                 }
                               }];
}

RCT_EXPORT_METHOD(rejectFriendApplication
                  : (NSString *)handle userID
                  : (NSString *)userID config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

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

  [zim rejectFriendApplicationFromUserID:userID
                                  config:configObj
                                callback:^(ZIMUserInfo *userInfo, ZIMError *_Nonnull errorInfo) {
                                  if (errorInfo.code == 0) {
                                    resolve(@{@"userInfo" : [self mZIMUserInfo:userInfo]});
                                  } else {
                                    reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                           errorInfo.message, nil);
                                  }
                                }];
}

RCT_EXPORT_METHOD(queryFriendsInfo
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim queryFriendsInfoByUserIDs:userIDs
                        callback:^(NSArray<ZIMFriendInfo *> *infos,
                                   NSArray<ZIMErrorUserInfo *> *errorUsers,
                                   ZIMError *_Nonnull errorInfo) {
                          if (errorInfo.code == 0) {
                            resolve(@{
                              @"friendInfos" : [self mZIMFriendInfoList:infos],
                              @"errorUserList" : [self mZIMErrorUserInfoList:errorUsers]
                            });
                          } else {
                            reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                   errorInfo.message, nil);
                          }
                        }];
}

RCT_EXPORT_METHOD(queryFriendList
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull errorInfo) {
                          if (errorInfo.code == 0) {
                            resolve(@{
                              @"nextFlag" : @(nextFlag),
                              @"friendList" : [self mZIMFriendInfoList:infos]
                            });
                          } else {
                            reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                   errorInfo.message, nil);
                          }
                        }];
}

RCT_EXPORT_METHOD(queryFriendApplicationList
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull errorInfo) {
                                     if (errorInfo.code == 0) {
                                       resolve(@{
                                         @"nextFlag" : @(nextFlag),
                                         @"applicationList" : [self mZIMFriendApplicationList:infos]
                                       });
                                     } else {
                                       reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                              errorInfo.message, nil);
                                     }
                                   }];
}

RCT_EXPORT_METHOD(addUsersToBlacklist
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim addUsersToBlacklistWithUserIDs:userIDs
                             callback:^(NSArray<ZIMErrorUserInfo *> *errorUsers,
                                        ZIMError *_Nonnull errorInfo) {
                               if (errorInfo.code == 0) {
                                 resolve(
                                     @{@"errorUserList" : [self mZIMErrorUserInfoList:errorUsers]});
                               } else {
                                 reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                        errorInfo.message, nil);
                               }
                             }];
}

RCT_EXPORT_METHOD(removeUsersFromBlacklist
                  : (NSString *)handle userIDs
                  : (NSArray<NSString *> *)userIDs resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim removeUsersFromBlacklistWithUserIDs:userIDs
                                  callback:^(NSArray<ZIMErrorUserInfo *> *errorUsers,
                                             ZIMError *_Nonnull errorInfo) {
                                    if (errorInfo.code == 0) {
                                      resolve(@{
                                        @"errorUserList" : [self mZIMErrorUserInfoList:errorUsers]
                                      });
                                    } else {
                                      reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                             errorInfo.message, nil);
                                    }
                                  }];
}

RCT_EXPORT_METHOD(checkUserIsInBlacklist
                  : (NSString *)handle userID
                  : (NSString *)userID resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    return;
  }

  [zim
      checkUserIsInBlacklistByUserID:userID
                            callback:^(BOOL isUserInBlacklist, ZIMError *_Nonnull errorInfo) {
                              if (errorInfo.code == 0) {
                                resolve(@{
                                  @"isUserInBlacklist" : [NSNumber numberWithBool:isUserInBlacklist]
                                });
                              } else {
                                reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                       errorInfo.message, nil);
                              }
                            }];
}

RCT_EXPORT_METHOD(queryBlacklist
                  : (NSString *)handle config
                  : (NSDictionary *)config resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  ZIM *zim = [self.engineMap objectForKey:handle];
  if (!zim) {
    reject(@"-1", @"no instance", nil);
    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 *_Nonnull errorInfo) {
                         if (errorInfo.code == 0) {
                           resolve(@{
                             @"nextFlag" : @(nextFlag),
                             @"blacklist" : [self mZIMUserInfoList:infos]
                           });
                         } else {
                           reject([NSString stringWithFormat:@"%lu", errorInfo.code],
                                  errorInfo.message, nil);
                         }
                       }];
}

#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 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 convertConversationFromOCToJs:changeInfo.conversation];
      [convInfoArray addObject:@{@"event" : @(changeInfo.event), @"conversation" : convDict}];
    }

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

- (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
    receivePeerMessage:(NSArray<ZIMMessage *> *)messageList
            fromUserID:(NSString *)fromUserID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"receivePeerMessage")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"messageList" : [self mZIMMessageList:messageList],
                           @"fromConversationID" : fromUserID
                         }
                       }];
  }
}

- (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" : @{
                           @"messageList" : [self mZIMMessageList:messageList],
                           @"info" : @{@"isOfflineMessage" : @(info.isOfflineMessage)},
                           @"fromConversationID" : fromUserID
                         }
                       }];
  }
}

- (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" : @{
                           @"messageList" : [self mZIMMessageList:messageList],
                           @"info" : @{@"isOfflineMessage" : @(info.isOfflineMessage)},
                           @"fromConversationID" : fromRoomID
                         }
                       }];
  }
}

- (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" : @{
                           @"messageList" : [self mZIMMessageList:messageList],
                           @"info" : @{@"isOfflineMessage" : @(info.isOfflineMessage)},
                           @"fromConversationID" : fromGroupID
                         }
                       }];
  }
}

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

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

- (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 convertMessageFromOCToJs: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 messageReactionsChanged:(NSArray<ZIMMessageReaction *> *)reactions {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"messageReactionsChanged")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{@"reactions" : [self mZIMMessageReactionList:reactions]}
                       }];
  }
}

- (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 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),
                           @"isDeleteConversationAllMessage" :
                               [NSNumber numberWithBool:deletedInfo.isDeleteConversationAllMessage],
                           @"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 convertMessageFromOCToJs: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
    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
    callInvitationAccepted:(ZIMCallInvitationAcceptedInfo *)info
                    callID:(NSString *)callID {
  if (self.hasListeners) {
    NSString *handle = [self.engineMapForCallback objectForKey:zim];
    [self sendEventWithName:RN_EVENT(@"callInvitationAccepted")
                       body:@{
                         @"handle" : handle,
                         @"data" : @{
                           @"callID" : callID,
                           @"invitee" : info.invitee,
                           @"extendedData" : info.extendedData
                         }
                       }];
  }
}

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

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

- (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]}
                       }];
  }
}

//==== convert ====//

- (nullable ZIMMessage *)convertMessageFromJsToOC:(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];

      NSString *str = [RCTConvert NSString:dict[@"message"]];
      NSArray *messageMap = [str componentsSeparatedByString:@","];
      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[@"conversationSeq"]] forKey:@"conversationSeq"];
  [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[@"isBroadcastMessage"]] forKey:@"isBroadcastMessage"];
  [msg safeSetValue:[RCTConvert NSNumber:dict[@"isServerMessage"]] forKey:@"isServerMessage"];
  [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 convertMessageFromJsToOC: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 convertLiteInfoFromJsToOC:msgdict];
      if (message) {
        [messageObjArray addObject:message];
      }
    }
    [msg safeSetValue:messageObjArray forKey:@"messageInfoList"];
  }

  return msg;
}

- (NSDictionary *)convertMessageFromOCToJs:(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.conversationSeq]
                     forKey:@"conversationSeq"];
  [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.isBroadcastMessage]
                     forKey:@"isBroadcastMessage"];
  [messageDic safeSetObject:[NSNumber numberWithBool:message.isServerMessage]
                     forKey:@"isServerMessage"];
  [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 convertRepliedInfo: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"];

  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 convertLiteInfo:message];
        [messageInfoArray addObject:msgDict];
      }
      [messageDic safeSetObject:messageInfoArray forKey:@"messageInfoList"];
      break;
    }
    default:
      break;
  }
  return messageDic;
}

- (NSDictionary *)convertLiteInfo:(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 convertLiteInfo:msgInfo];
        [messageInfoArray addObject:msgInfoDict];
      }
      [messageDic safeSetObject:messageInfoArray forKey:@"messageInfoList"];
      break;
    }
    default:
      break;
  }

  return messageDic;
}

- (nullable ZIMMessageLiteInfo *)convertLiteInfoFromJsToOC:(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 convertLiteInfoFromJsToOC:msgdict];
      if (message) {
        [messageObjArray addObject:message];
      }
    }
    [msg safeSetValue:messageObjArray forKey:@"messageInfoList"];
  }

  return msg;
}

- (NSDictionary *)convertRepliedInfo:(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 convertLiteInfo:message] forKey:@"messageInfo"];
  return infoDic;
}

- (nullable ZIMConversation *)convertConversationFromJsToOC:(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 safeSetValue:[RCTConvert NSNumber:dict[@"isPinned"]] forKey:@"isPinned"];
  conversation.draft = [RCTConvert NSString:dict[@"draft"]];

  return conversation;
}

- (NSDictionary *)convertConversationFromOCToJs:(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 numberWithBool:conversation.isPinned]
                          forKey:@"isPinned"];
  [conversationDic safeSetObject:conversation.marks forKey:@"marks"];
  [conversationDic safeSetObject:conversation.draft forKey:@"draft"];
  [conversationDic safeSetObject:[self convertMessageFromOCToJs:conversation.lastMessage]
                          forKey:@"lastMessage"];

  if (conversation.mentionedInfoList != nil) {
    NSMutableArray *mentionArray = [NSMutableArray array];
    for (ZIMMessageMentionedInfo *info in conversation.mentionedInfoList) {
      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"];

      [mentionArray addObject:dic];
    }
    [conversationDic safeSetObject:mentionArray forKey:@"mentionedInfoList"];
  }

  if ([conversation isKindOfClass:[ZIMGroupConversation class]]) {
    ZIMGroupConversation *groupConv = (ZIMGroupConversation *)conversation;
    [conversationDic safeSetObject:[NSNumber numberWithBool:groupConv.isDisabled]
                            forKey:@"isDisabled"];
    [conversationDic safeSetObject:[NSNumber numberWithLongLong:groupConv.mutedExpiredTime]
                            forKey:@"mutedExpiredTime"];
  }

  return conversationDic;
}

- (NSDictionary *)convertMessageReactionFromOCToJs:(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:[self mZIMMessageReactionUserInfoList:info.userList] forKey:@"userList"];

  return dic;
}

- (NSArray *)convertNumberToStringList:(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[@"userID"] = info.userID;
  resultMap[@"userName"] = info.userName;
  resultMap[@"userAvatarUrl"] = info.userAvatarUrl;
  resultMap[@"memberRole"] = @(info.memberRole);
  resultMap[@"memberNickname"] = info.memberNickname;
  resultMap[@"muteExpiredTime"] = @(info.muteExpiredTime);
  resultMap[@"memberAvatarUrl"] = info.userAvatarUrl;

  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 *)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[@"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 *)mZIMGroup:(ZIMGroup *)group {
  NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];

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

  return resultMap;
}

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

  resultMap[@"userID"] = info.operatedUserInfo.userID;
  resultMap[@"userName"] = info.operatedUserInfo.userName;
  resultMap[@"memberRole"] = @(info.operatedUserInfo.memberRole);
  resultMap[@"memberNickname"] = info.operatedUserInfo.memberNickname;
  resultMap[@"operatedUserInfo"] = [self mZIMGroupMemberInfo:info.operatedUserInfo];

  return resultMap;
}

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

  for (ZIMGroupMemberInfo *info in infos) {
    [resultArray addObject:[self mZIMGroupMemberInfo: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 *)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 *)convertVoIPConfigFromJsToOC:(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 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 safeSetValue:[RCTConvert NSNumber:dict[@"badgeIncrement"]]
                      forKey:@"badgeIncrement"];
    [pushConfig safeSetValue:[RCTConvert NSNumber:dict[@"enableBadge"]] forKey:@"enableBadge"];
    pushConfig.voIPConfig = [self convertVoIPConfigFromJsToOC:dict[@"voIPConfig"]];
    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 numberWithLongLong:info.messageID] stringValue]
                forKey:@"messageID"];
    [dic safeSetObject:[NSNumber numberWithBool:info.isSelfOperated] forKey:@"isSelfOperated"];

    [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 convertMessageFromOCToJs: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 convertMessageReactionFromOCToJs: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[@"userID"] = info.userID;
  resultMap[@"userName"] = info.userName;
  resultMap[@"userAvatarUrl"] = info.userAvatarUrl;

  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"];
  resultMap[@"userAvatarUrl"] = info.baseInfo.userAvatarUrl;
  resultMap[@"extendedData"] = info.extendedData;

  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[@"userID"] = info.userID;
  resultMap[@"userName"] = info.userName;
  resultMap[@"userAvatarUrl"] = info.userAvatarUrl;
  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[@"userID"] = info.userID;
  resultMap[@"userName"] = info.userName;
  resultMap[@"userAvatarUrl"] = info.userAvatarUrl;

  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);
  }

  return resultMap;
}

@end
