
//  Created by Tencent on 2023/06/09.
//  Copyright © 2023 Tencent. All rights reserved.

#import <AVFoundation/AVFoundation.h>

#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/TIMDefine.h>
#import <TIMCommon/TUIMessageCell.h>
#import <TIMCommon/TUISystemMessageCellData.h>
#import <TIMCommon/TUIRelationUserModel.h>
#import <TUICore/NSString+TUIUtil.h>
#import <TUICore/TUICore.h>
#import <TUICore/TUILogin.h>
#import <TUICore/TUITool.h>
#import <TUICore/OfflinePushExtInfo.h>
#import "TUIChatConfig.h"
#import "TUICloudCustomDataTypeCenter.h"
#import "TUIMessageBaseDataProvider.h"
#import "TUIMessageProgressManager.h"
#import "TUITypingStatusCellData.h"

/**
 * Date time interval above the message in the UIMessageCell, in seconds, default is (5 * 60)
 */
#define MaxDateMessageDelay 5 * 60

@interface TUIMessageBaseDataProvider () <V2TIMAdvancedMsgListener, V2TIMGroupListener,TUIMessageProgressManagerDelegate>
@property(nonatomic, strong) TUIChatConversationModel *conversationModel;
@property(nonatomic, strong) NSMutableArray<TUIMessageCellData *> *uiMsgs_;
@property(nonatomic, strong) NSMutableSet<NSString *> *sentReadGroupMsgSet;
@property(nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *heightCache_;
@property(nonatomic, assign) BOOL isLoadingData;
@property(nonatomic, assign) BOOL isNoMoreMsg;
@property(nonatomic, assign) BOOL isFirstLoad;
@property(nonatomic, strong) V2TIMMessage *lastMsg;
@property(nonatomic, strong) V2TIMMessage *msgForDate;
@property(nonatomic, strong) V2TIMGroupMemberFullInfo *groupSelfInfo;
@property(nonatomic, strong) NSMutableArray<V2TIMMessage *> *groupPinList;
@property(nonatomic, assign) V2TIMGroupMemberRole changedRole;
@property(nonatomic, strong) V2TIMGroupInfo *groupInfo;
@end

@implementation TUIMessageBaseDataProvider

- (instancetype)initWithConversationModel:(TUIChatConversationModel *)conversationModel {
    self = [super init];
    if (self) {
        _conversationModel = conversationModel;
        _isLoadingData = NO;
        _isNoMoreMsg = NO;
        _pageCount = 20;
        _isFirstLoad = YES;
        [self registerTUIKitNotification];
    }
    return self;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (NSMutableSet<NSString *> *)sentReadGroupMsgSet {
    if (_sentReadGroupMsgSet == nil) {
        _sentReadGroupMsgSet = [NSMutableSet setWithCapacity:10];
    }
    return _sentReadGroupMsgSet;
}

- (NSMutableArray<TUIMessageCellData *> *)uiMsgs_ {
    if (_uiMsgs_ == nil) {
        _uiMsgs_ = [NSMutableArray array];
    }
    return _uiMsgs_;
}

- (NSMutableDictionary<NSString *, NSNumber *> *)heightCache_ {
    if (_heightCache_ == nil) {
        _heightCache_ = [NSMutableDictionary dictionary];
    }
    return _heightCache_;
}

- (NSArray<TUIMessageCellData *> *)uiMsgs {
    return _uiMsgs_;
}
- (NSDictionary<NSString *, NSNumber *> *)heightCache {
    return _heightCache_;
}

- (void)setChangedRole:(V2TIMGroupMemberRole)changedRole {
    _changedRole = changedRole;
    if (self.groupRoleChanged) {
        self.groupRoleChanged(changedRole);
    }
}

#pragma mark - TUIKitNotification
- (void)registerTUIKitNotification {
    [[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
    [[V2TIMManager sharedInstance] addGroupListener:self];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(onMessageStatusChanged:)
                                                 name:TUIKitNotification_onMessageStatusChanged
                                               object:nil];
}

- (void)onMessageStatusChanged:(NSNotification *)notification {
    V2TIMMessage *targetMsg = notification.object;
    NSString *msgId = targetMsg.msgID;
    TUIMessageCellData *uiMsg = nil;
    BOOL isMatch = NO;
    for (uiMsg in self.uiMsgs) {
        if ([uiMsg.msgID isEqualToString:msgId]) {
            [self.dataSource dataProviderDataSourceWillChange:self];
            NSInteger index = [self.uiMsgs indexOfObject:uiMsg];
            [self.dataSource dataProviderDataSourceChange:self withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload atIndex:index animation:YES];
            [self.dataSource dataProviderDataSourceDidChange:self];
            isMatch = YES;
            break;
        }
    }
    if (!isMatch) {
        // Need insert UI Message
        [self onRecvNewMessage:targetMsg];
    }
}

#pragma mark - V2TIMAdvancedMsgListener
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
    // immsg -> uimsg
    NSMutableArray *newUIMsgs = [self transUIMsgFromIMMsg:@[ msg ]];
    if (newUIMsgs.count == 0) {
        return;
    }

    TUIMessageCellData *newUIMsg = newUIMsgs.lastObject;
    newUIMsg.source = Msg_Source_OnlinePush;

    if ([newUIMsg isKindOfClass:TUITypingStatusCellData.class]) {
        if (![TUIChatConfig defaultConfig].enableTypingStatus) {
            return;
        }

        TUITypingStatusCellData *stastusData = (TUITypingStatusCellData *)newUIMsg;

        if (!NSThread.isMainThread) {
            @weakify(self);
            dispatch_async(dispatch_get_main_queue(), ^{
              @strongify(self);
              [self dealTypingByStatusCellData:stastusData];
            });
            return;
        } else {
            [self dealTypingByStatusCellData:stastusData];
        }

        return;
    }

    @weakify(self);
    [self preProcessMessage:newUIMsgs
                   callback:^{
                     @strongify(self);
                     [self.dataSource dataProviderDataSourceWillChange:self];
                     @autoreleasepool {
                         for (TUIMessageCellData *uiMsg in newUIMsgs) {
                             [self addUIMsg:uiMsg];
                             [self.dataSource dataProviderDataSourceChange:self
                                                                  withType:TUIMessageBaseDataProviderDataSourceChangeTypeInsert
                                                                   atIndex:(self.uiMsgs_.count - 1)
                                                                 animation:YES];
                         }
                     }
                     [self.dataSource dataProviderDataSourceDidChange:self];

                     if ([self.dataSource respondsToSelector:@selector(dataProvider:ReceiveNewUIMsg:)]) {
                         /**
                          * Note that firstObject cannot be taken here, firstObject may be SystemMessageCellData that displays system time
                          */
                         [self.dataSource dataProvider:self ReceiveNewUIMsg:newUIMsgs.lastObject];
                     }
                   }];
}

- (NSMutableArray *)transUIMsgFromIMMsg:(NSArray *)msgs {
    NSMutableArray *uiMsgs = [NSMutableArray array];
    for (NSInteger k = msgs.count - 1; k >= 0; --k) {
        V2TIMMessage *msg = msgs[k];
        /**
         * Messages that are not the current session, ignore them directly
         */
        if (![msg.userID isEqualToString:self.conversationModel.userID] && ![msg.groupID isEqualToString:self.conversationModel.groupID]) {
            continue;
        }

        TUIMessageCellData *cellData = nil;
        /**
         * Determine whether it is a custom message outside the component
         */
        if ([self.dataSource respondsToSelector:@selector(dataProvider:CustomCellDataFromNewIMMessage:)]) {
            cellData = [self.dataSource dataProvider:self CustomCellDataFromNewIMMessage:msg];
        }

        /**
         * Determine whether it is a component internal message
         */
        if (!cellData) {
            cellData = [self.class getCellData:msg];
        }
        if (cellData) {
            TUIMessageCellData *dateMsg = [self getSystemMsgFromDate:msg.timestamp];
            if (dateMsg) {
                if (self.mergeAdjacentMsgsFromTheSameSender) {
                    dateMsg.showName = NO;
                }
                self.msgForDate = msg;
                [uiMsgs addObject:dateMsg];
            }
            if (self.mergeAdjacentMsgsFromTheSameSender) {
                cellData.showName = NO;
            }
            [uiMsgs addObject:cellData];
        }
    }
    return uiMsgs;
}

/// Received message read receipts, both in group and c2c conversation.
- (void)onRecvMessageReadReceipts:(NSArray<V2TIMMessageReceipt *> *)receiptList {
    if (receiptList.count == 0) {
        NSLog(@"group receipt data is empty, ignore");
        return;
    }
    if (![self.dataSource respondsToSelector:@selector(dataProvider:ReceiveReadMsgWithGroupID:msgID:readCount:unreadCount:)]) {
        NSLog(@"data source can not respond to protocol, ignore");
    }
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    for (V2TIMMessageReceipt *receipt in receiptList) {
        [dict setObject:receipt forKey:receipt.msgID];
    }
    // update TUIMessageCellData readCount/unreadCount
    for (TUIMessageCellData *data in self.uiMsgs) {
        if ([dict.allKeys containsObject:data.innerMessage.msgID]) {
            V2TIMMessageReceipt *receipt = dict[data.innerMessage.msgID];
            data.messageReceipt = receipt;
            if ([self.dataSource respondsToSelector:@selector(dataProvider:ReceiveReadMsgWithGroupID:msgID:readCount:unreadCount:)]) {
                [self.dataSource dataProvider:self
                    ReceiveReadMsgWithGroupID:receipt.groupID
                                        msgID:receipt.msgID
                                    readCount:receipt.readCount
                                  unreadCount:receipt.unreadCount];
            }
        }
    }
}
- (void)onRecvMessageRevoked:(NSString *)msgID operateUser:(V2TIMUserFullInfo *)operateUser reason:(NSString *)reason {
    @weakify(self);
    [TUITool dispatchMainAsync:^{
      @strongify(self);
      TUIMessageCellData *uiMsg = nil;
      for (uiMsg in self.uiMsgs) {
          if ([uiMsg.msgID isEqualToString:msgID]) {
              [self.dataSource dataProviderDataSourceWillChange:self];
              NSUInteger index = [self.uiMsgs indexOfObject:uiMsg];
              TUISystemMessageCellData *revokeCellData = (TUISystemMessageCellData *)[self.class getRevokeCellData:uiMsg.innerMessage];
              revokeCellData.content = [self.class getRevokeDispayString:uiMsg.innerMessage operateUser:operateUser reason:reason];
              if(![operateUser.userID isEqualToString:uiMsg.innerMessage.sender]) {
                  //Super User revoke
                  revokeCellData.supportReEdit = NO;
              }
              [self replaceUIMsg:revokeCellData atIndex:index];
              [self.dataSource dataProviderDataSourceChange:self withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload atIndex:index animation:YES];
              [self.dataSource dataProviderDataSourceDidChange:self];
              break;
          }
      }

      if ([self.dataSource respondsToSelector:@selector(dataProvider:ReceiveRevokeUIMsg:)]) {
          [self.dataSource dataProvider:self ReceiveRevokeUIMsg:uiMsg];
      }
    }];
}

- (void)onRecvMessageModified:(V2TIMMessage *)msg {
    V2TIMMessage *imMsg = msg;
    if (imMsg == nil || ![imMsg isKindOfClass:V2TIMMessage.class]) {
        return;
    }

    @weakify(self);
    for (TUIMessageCellData *uiMsg in self.uiMsgs) {
        if ([uiMsg.msgID isEqualToString:imMsg.msgID]) {
            if ([uiMsg customReloadCellWithNewMsg:imMsg]) {
                return;
            }
            NSMutableArray *newUIMsgs = [self transUIMsgFromIMMsg:@[ imMsg ]];
            if (newUIMsgs.count == 0) {
                return;
            }
            /**
             * Note that firstObject cannot be taken here, firstObject may be SystemMessageCellData that displays system time
             */
            TUIMessageCellData *newUIMsg = newUIMsgs.lastObject;
            newUIMsg.messageReceipt = uiMsg.messageReceipt;
            [self preProcessMessage:@[newUIMsg] callback:^{
                @strongify(self);
                NSInteger index = [self.uiMsgs indexOfObject:uiMsg];
                if (index < self.uiMsgs.count) {
                    [self.dataSource dataProviderDataSourceWillChange:self];
                    [self replaceUIMsg:newUIMsg atIndex:index];
                    [self.dataSource dataProviderDataSourceChange:self
                                                         withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload
                                                          atIndex:index
                                                        animation:YES];
                    [self.dataSource dataProviderDataSourceDidChange:self];
                }
            }];
            return;
        }
    }
}

- (void)dealTypingByStatusCellData:(TUITypingStatusCellData *)stastusData {
    if (1 == stastusData.typingStatus) {
        // The timer is retimed upon receipt of the notification from the other party's input
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resetTypingStatus) object:nil];

        self.conversationModel.otherSideTyping = YES;
        // If the other party does not continue typing, end the status every 5 seconds
        [self performSelector:@selector(resetTypingStatus) withObject:nil afterDelay:5.0];
    } else {
        self.conversationModel.otherSideTyping = NO;
    }
}

- (void)resetTypingStatus {
    self.conversationModel.otherSideTyping = NO;
}

#pragma mark - Msgs
- (void)loadMessageSucceedBlock:(void (^)(BOOL isFirstLoad, BOOL isNoMoreMsg, NSArray<TUIMessageCellData *> *newMsgs))succeedBlock
                      FailBlock:(V2TIMFail)failBlock {
    if (self.isLoadingData || self.isNoMoreMsg) {
        failBlock(ERR_SUCC, @"refreshing");
        return;
    }
    self.isLoadingData = YES;

    @weakify(self);
    if (self.conversationModel.userID.length > 0) {
        [[V2TIMManager sharedInstance] getC2CHistoryMessageList:self.conversationModel.userID
            count:self.pageCount
            lastMsg:self.lastMsg
            succ:^(NSArray<V2TIMMessage *> *msgs) {
              @strongify(self);
              if (msgs.count != 0) {
                  self.lastMsg = msgs[msgs.count - 1];
              }
              [self loadMessages:msgs SucceedBlock:succeedBlock];
            }
            fail:^(int code, NSString *desc) {
              @strongify(self);
              self.isLoadingData = NO;
              if (failBlock) {
                  failBlock(code, desc);
              }
            }];
    } else if (self.conversationModel.groupID.length > 0) {
        [[V2TIMManager sharedInstance] getGroupHistoryMessageList:self.conversationModel.groupID
            count:self.pageCount
            lastMsg:self.lastMsg
            succ:^(NSArray<V2TIMMessage *> *msgs) {
              @strongify(self);
              if (msgs.count != 0) {
                  self.lastMsg = msgs[msgs.count - 1];
              }
              [self loadMessages:msgs SucceedBlock:succeedBlock];
            }
            fail:^(int code, NSString *desc) {
              @strongify(self);
              self.isLoadingData = NO;
              if (failBlock) {
                  failBlock(code, desc);
              }
            }];
    }
}

- (void)loadMessages:(NSArray<V2TIMMessage *> *)msgs
        SucceedBlock:(void (^)(BOOL isFirstLoad, BOOL isNoMoreMsg, NSArray<TUIMessageCellData *> *newMsgs))succeedBlock {
    NSMutableArray<TUIMessageCellData *> *uiMsgs = [self transUIMsgFromIMMsg:msgs];
    if (uiMsgs.count == 0) {
        return;
    }
    @weakify(self);
    [self preProcessMessage:uiMsgs
                   callback:^{
                     @strongify(self);
                     if (msgs.count < self.pageCount) {
                         self.isNoMoreMsg = YES;
                     }
                     if (uiMsgs.count != 0) {
                         NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, uiMsgs.count)];
                         [self insertUIMsgs:uiMsgs atIndexes:indexSet];
                     }

                     self.isLoadingData = NO;
                     if (succeedBlock) {
                         succeedBlock(self.isFirstLoad, self.isNoMoreMsg, uiMsgs);
                     }
                     self.isFirstLoad = NO;
                   }];
}

- (void)preProcessMessage:(NSArray<TUIMessageCellData *> *)uiMsgs callback:(void (^)(void))callback {
    // Synchronous message processing: Messages can be preprocessed here, and complex and time-consuming operations are not recommended.
    [self processMessageSync:uiMsgs callback:callback];

    // Asynchronous processing of messages: Please handle complex time-consuming operations in this function and update them in the form of reload Cell.
    [self processMessageAsync:uiMsgs];
}

- (void)processMessageSync:(NSArray<TUIMessageCellData *> *)uiMsgs callback:(void (^)(void))callback {
    if (callback) {
        callback();
    }
}

- (void)processMessageAsync:(NSArray<TUIMessageCellData *> *)uiMsgs {
    __weak typeof(self) weakSelf = self;

    [self getReactFromMessage:uiMsgs];

    [self requestForAdditionalUserInfo:uiMsgs
                              callback:^{
                                dispatch_async(dispatch_get_main_queue(), ^{
                                  [weakSelf.dataSource dataProviderDataSourceWillChange:weakSelf];
                                  for (TUIMessageCellData *uiMsg in uiMsgs) {
                                      NSArray *userIDList = [weakSelf getUserIDListForAdditionalUserInfo:@[ uiMsg ]];
                                      if (userIDList.count == 0) {
                                          continue;
                                      }
                                      NSUInteger index = [weakSelf.uiMsgs indexOfObject:uiMsg];
                                      if (index != NSNotFound) {
                                        [weakSelf.dataSource dataProvider:weakSelf onRemoveHeightCache:uiMsg];
                                        [weakSelf.dataSource dataProviderDataSourceChange:weakSelf
                                                                                 withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload
                                                                                  atIndex:index
                                                                                animation:YES];                                          
                                      }
                                  }
                                  [weakSelf.dataSource dataProviderDataSourceDidChange:weakSelf];
                                });

                                [weakSelf processQuoteMessage:uiMsgs];
                              }];
}

- (void)getReactFromMessage:(NSArray<TUIMessageCellData *> *)uiMsgs {
    if (uiMsgs.count == 0) {
        return;
    }
    // fetch react
    [[NSNotificationCenter defaultCenter] postNotificationName:@"TUIKitFetchReactNotification" object:uiMsgs];
}

- (void)requestForAdditionalUserInfo:(NSArray<TUIMessageCellData *> *)uiMsgs callback:(void (^)(void))callback {
    NSArray *userIDList = [self getUserIDListForAdditionalUserInfo:uiMsgs];
    if (userIDList.count == 0) {
        if (callback) {
            callback();
        }
        return;
    }

    [self requestForUserDetailsInfo:userIDList
                           callback:^(NSDictionary<NSString *, TUIRelationUserModel *> *result) {
                             for (TUIMessageCellData *cellData in uiMsgs) {
                                 NSMutableDictionary *additionalUserInfoResult = [NSMutableDictionary dictionary];
                                 NSArray *userIDList = [self getUserIDListForAdditionalUserInfo:@[ cellData ]];
                                 for (NSString *userID in userIDList) {
                                     TUIRelationUserModel *userInfo = [result objectForKey:userID];
                                     if (userID.length > 0 && userInfo) {
                                         additionalUserInfoResult[userID] = userInfo;
                                     }
                                 }
                                 cellData.additionalUserInfoResult = additionalUserInfoResult;
                             }
                             if (callback) {
                                 callback();
                             }
                           }];
}

- (void)requestForUserDetailsInfo:(NSArray *)userIDList callback:(void (^)(NSDictionary<NSString *, TUIRelationUserModel *> *))callback {
    if (callback == nil) {
        return;
    }

    NSMutableDictionary<NSString *, TUIRelationUserModel *> *result = [NSMutableDictionary dictionary];
    if (self.conversationModel.groupID.length > 0) {
        [[V2TIMManager sharedInstance] getGroupMembersInfo:self.conversationModel.groupID
            memberList:userIDList
            succ:^(NSArray<V2TIMGroupMemberFullInfo *> *memberList) {
              [memberList enumerateObjectsUsingBlock:^(V2TIMGroupMemberFullInfo *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
                if (obj.userID.length > 0) {
                    TUIRelationUserModel *userInfo = [[TUIRelationUserModel alloc] init];
                    userInfo.userID = obj.userID;
                    userInfo.nickName = obj.nickName;
                    userInfo.friendRemark = obj.friendRemark;
                    userInfo.nameCard = obj.nameCard;
                    userInfo.faceURL = obj.faceURL;
                    [result setObject:userInfo forKey:userInfo.userID];
                }
              }];
              callback(result);
            }
            fail:^(int code, NSString *desc) {
              callback(result);
            }];
    } else {
        [V2TIMManager.sharedInstance getFriendsInfo:userIDList
            succ:^(NSArray<V2TIMFriendInfoResult *> *resultList) {
              for (V2TIMFriendInfoResult *item in resultList) {
                  if (item.friendInfo && item.friendInfo.userID.length > 0) {
                      TUIRelationUserModel *userInfo = [[TUIRelationUserModel alloc] init];
                      userInfo.userID = item.friendInfo.userID;
                      userInfo.nickName = item.friendInfo.userFullInfo.nickName;
                      userInfo.friendRemark = item.friendInfo.friendRemark;
                      userInfo.faceURL = item.friendInfo.userFullInfo.faceURL;
                      [result setObject:userInfo forKey:userInfo.userID];
                  }
              }
              callback(result);
            }
            fail:^(int code, NSString *desc) {
              callback(result);
            }];
    }
}

- (NSArray<NSString *> *)getUserIDListForAdditionalUserInfo:(NSArray<TUIMessageCellData *> *)uiMsgs {
    NSMutableSet *userIDSet = [NSMutableSet set];

    // Built-in
    for (TUIMessageCellData *cellData in uiMsgs) {
        if (![cellData.messageModifyReplies isKindOfClass:NSArray.class] || cellData.messageModifyReplies.count == 0) {
            continue;
        }
        [cellData.messageModifyReplies enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
          if (obj && [obj isKindOfClass:NSDictionary.class]) {
              NSDictionary *dic = (NSDictionary *)obj;
              if (IS_NOT_EMPTY_NSSTRING(dic[@"messageSender"])) {
                  [userIDSet addObject:dic[@"messageSender"]];
              }
          }
        }];
    }

    // Get userid from cellData which override the -requestForAdditionalUserInfo method
    for (TUIMessageCellData *cellData in uiMsgs) {
        if ([cellData respondsToSelector:@selector(requestForAdditionalUserInfo)]) {
            NSArray *array = [cellData requestForAdditionalUserInfo];
            if (array && array.count > 0) {
                [userIDSet addObjectsFromArray:array];
            }
        }
    }

    NSArray *userIDList = userIDSet.allObjects;
    return userIDList;
}

- (void)processQuoteMessage:(NSArray<TUIMessageCellData *> *)uiMsgs {
    // Subclasses implement this method
    return;
}

- (void)sendUIMsg:(TUIMessageCellData *)uiMsg
    toConversation:(TUIChatConversationModel *)conversationData
     willSendBlock:(void (^)(BOOL isReSend, TUIMessageCellData *dateUIMsg))willSendBlock
         SuccBlock:(nullable V2TIMSucc)succ
         FailBlock:(nullable V2TIMFail)fail {
    [self preProcessMessage:@[ uiMsg ]
                   callback:^{
                     [TUITool dispatchMainAsync:^{
                       V2TIMMessage *imMsg = uiMsg.innerMessage;
                       TUIMessageCellData *placeholderCellData = uiMsg.placeHolderCellData;
                       TUIMessageCellData *dateMsg = nil;
                       BOOL isReSent = NO;
                       if (uiMsg.status == Msg_Status_Init) {
                           // New message
                           dateMsg = [self getSystemMsgFromDate:imMsg.timestamp];
                       } else if (imMsg) {
                           // Re-sent
                           isReSent = YES;
                           dateMsg = [self getSystemMsgFromDate:[NSDate date]];
                       } else {
                           if (fail) {
                               fail(ERR_INVALID_PARAMETERS, @"Unknown message state");
                           }
                           return;
                       }

                       imMsg.isExcludedFromUnreadCount = [TUIConfig defaultConfig].isExcludedFromUnreadCount;
                       imMsg.isExcludedFromLastMessage = [TUIConfig defaultConfig].isExcludedFromLastMessage;

                       // Update sender
                       uiMsg.identifier = [TUILogin getUserID];

                       [self.dataSource dataProviderDataSourceWillChange:self];

                       // Handle data
                       if (isReSent) {
                           NSInteger row = [self.uiMsgs indexOfObject:uiMsg];
                           [self removeUImsgAtIndex:row];
                           [self.dataSource dataProviderDataSourceChange:self
                                                                withType:TUIMessageBaseDataProviderDataSourceChangeTypeDelete
                                                                 atIndex:row
                                                               animation:YES];
                       }
                      if (placeholderCellData) {
                          NSInteger row = [self.uiMsgs indexOfObject:placeholderCellData];
                          if (row != NSNotFound) {
                              [self replaceUIMsg:uiMsg atIndex:row];
                              [self.dataSource dataProviderDataSourceChange:self
                                                                   withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload
                                                                    atIndex:row
                                                                  animation:NO];
                          }
                      }
                      else {
                          if (dateMsg) {
                              [self addUIMsg:dateMsg];
                              [self.dataSource dataProviderDataSourceChange:self
                                                                   withType:TUIMessageBaseDataProviderDataSourceChangeTypeInsert
                                                                    atIndex:(self.uiMsgs.count - 1)
                                                                  animation:YES];
                          }
                          [self addUIMsg:uiMsg];
                          [self.dataSource dataProviderDataSourceChange:self
                                                               withType:TUIMessageBaseDataProviderDataSourceChangeTypeInsert
                                                                atIndex:(self.uiMsgs.count - 1)
                                                              animation:YES];
                      }

                       [self.dataSource dataProviderDataSourceDidChange:self];

                       if (willSendBlock) {
                           willSendBlock(isReSent, dateMsg);
                       }

                       if (dateMsg) {
                           self.msgForDate = imMsg;
                       }

                       TUISendMessageAppendParams *appendParams = [[TUISendMessageAppendParams alloc] init];
                       appendParams.isSendPushInfo = YES;
                       appendParams.isOnlineUserOnly = NO;
                       appendParams.priority = V2TIM_PRIORITY_NORMAL;
                       uiMsg.msgID = [self.class sendMessage:imMsg
                           toConversation:conversationData
                           appendParams:appendParams
                           Progress:^(uint32_t progress) {
                             [TUIMessageProgressManager.shareManager appendUploadProgress:uiMsg.msgID progress:progress];
                           }

                           SuccBlock:^{
                           [TUIMessageProgressManager.shareManager appendUploadProgress:uiMsg.msgID progress:100];
                            if (succ) {
                                 succ();
                             }
                             [TUIMessageProgressManager.shareManager notifyMessageSendingResult:uiMsg.msgID result:TUIMessageSendingResultTypeSucc];
                           }
                           FailBlock:^(int code, NSString *desc) {
                             if (fail) {
                                 fail(code, desc);
                             }
                             [TUIMessageProgressManager.shareManager notifyMessageSendingResult:uiMsg.msgID result:TUIMessageSendingResultTypeFail];
                           }];
                     }];
                   }];
}

- (void)revokeUIMsg:(TUIMessageCellData *)uiMsg SuccBlock:(nullable V2TIMSucc)succ FailBlock:(nullable V2TIMFail)fail {
    V2TIMMessage *imMsg = uiMsg.innerMessage;
    if (imMsg == nil) {
        if (fail) {
            fail(ERR_INVALID_PARAMETERS, @"cellData.innerMessage is nil");
        }
        return;
    }
    NSInteger index = [self.uiMsgs indexOfObject:uiMsg];
    if (index == NSNotFound) {
        if (fail) {
            fail(ERR_INVALID_PARAMETERS, @"not found cellData in uiMsgs");
        }
        return;
    }

    @weakify(self);
    [self.class revokeMessage:imMsg
                         succ:^{
                           @strongify(self);
                           if (succ) {
                               succ();
                           }
                         }
                         fail:fail];
}

- (void)deleteUIMsgs:(NSArray<TUIMessageCellData *> *)uiMsgs SuccBlock:(nullable V2TIMSucc)succ FailBlock:(nullable V2TIMFail)fail {
}

- (void)addUIMsg:(TUIMessageCellData *)cellData {
    [self.uiMsgs_ addObject:cellData];
    if (self.mergeAdjacentMsgsFromTheSameSender) {
        [self.class updateUIMsgStatus:cellData uiMsgs:self.uiMsgs_];
    }
}

- (void)removeUIMsg:(TUIMessageCellData *)cellData {
    if (cellData) {
        [self.uiMsgs_ removeObject:cellData];
        if ([self.dataSource respondsToSelector:@selector(dataProvider:onRemoveHeightCache:)]) {
            [self.dataSource dataProvider:self onRemoveHeightCache:cellData];
        }
        if (self.mergeAdjacentMsgsFromTheSameSender) {
            [self.class updateUIMsgStatus:cellData uiMsgs:self.uiMsgs_];
        }
    }
}
- (void)sendPlaceHolderUIMessage:(TUIMessageCellData *)placeHolderCellData {
    V2TIMMessage *imMsg = placeHolderCellData.innerMessage;
    TUIMessageCellData *dateMsg = nil;
    if (placeHolderCellData.status == Msg_Status_Init) {
        // New message
        dateMsg = [self getSystemMsgFromDate:imMsg.timestamp];
    }

    [self.dataSource dataProviderDataSourceWillChange:self];

    if (dateMsg) {
        [self addUIMsg:dateMsg];
        [self.dataSource dataProviderDataSourceChange:self
                                             withType:TUIMessageBaseDataProviderDataSourceChangeTypeInsert
                                              atIndex:(self.uiMsgs.count - 1)
                                            animation:YES];
    }

    
    [self addUIMsg:placeHolderCellData];
    [self.dataSource dataProviderDataSourceChange:self
                                         withType:TUIMessageBaseDataProviderDataSourceChangeTypeInsert
                                          atIndex:(self.uiMsgs.count - 1)
                                        animation:YES];
    [self.dataSource dataProviderDataSourceDidChange:self];

}


- (void)insertUIMsgs:(NSArray<TUIMessageCellData *> *)uiMsgs atIndexes:(NSIndexSet *)indexes {
    [self.uiMsgs_ insertObjects:uiMsgs atIndexes:indexes];
    if (self.mergeAdjacentMsgsFromTheSameSender) {
        for (TUIMessageCellData *cellData in uiMsgs) {
            [self.class updateUIMsgStatus:cellData uiMsgs:self.uiMsgs_];
        }
    }
}

- (void)addUIMsgs:(NSArray<TUIMessageCellData *> *)uiMsgs {
    [self.uiMsgs_ addObjectsFromArray:uiMsgs];
    if (self.mergeAdjacentMsgsFromTheSameSender) {
        for (TUIMessageCellData *cellData in uiMsgs) {
            [self.class updateUIMsgStatus:cellData uiMsgs:self.uiMsgs_];
        }
    }
}

- (void)removeUIMsgList:(NSArray<TUIMessageCellData *> *)cellDatas {
    for (TUIMessageCellData *uiMsg in cellDatas) {
        [self removeUIMsg:uiMsg];
    }
}

- (void)removeUImsgAtIndex:(NSUInteger)index {
    if (index < self.uiMsgs.count) {
        TUIMessageCellData *msg = self.uiMsgs[index];
        [self removeUIMsg:msg];
    }
}

- (void)clearUIMsgList {
    NSArray *clearArray = [NSArray arrayWithArray:self.uiMsgs];
    [self removeUIMsgList:clearArray];
    self.msgForDate = nil;
    self.uiMsgs_ = nil;
}

- (void)replaceUIMsg:(TUIMessageCellData *)cellData atIndex:(NSUInteger)index {
    if (index < self.uiMsgs.count) {
        TUIMessageCellData *oldMsg = self.uiMsgs[index];
        if ([self.dataSource respondsToSelector:@selector(dataProvider:onRemoveHeightCache:)]) {
            [self.dataSource dataProvider:self onRemoveHeightCache:oldMsg];
        }
        
        [self.uiMsgs_ replaceObjectAtIndex:index withObject:cellData];
        
        if (self.mergeAdjacentMsgsFromTheSameSender) {
            [self.class updateUIMsgStatus:cellData uiMsgs:self.uiMsgs_];
        }
    }
}

- (void)sendLatestMessageReadReceipt {
    [self sendMessageReadReceiptAtIndexes:@[ @(self.uiMsgs.count - 1) ]];
}

- (void)sendMessageReadReceiptAtIndexes:(NSArray *)indexes {
    if (indexes.count == 0) {
        NSLog(@"sendMessageReadReceipt, but indexes is empty, ignore");
        return;
    }
    NSMutableArray *array = [NSMutableArray array];
    for (NSNumber *i in indexes) {
        if ([i intValue] < 0 || [i intValue] >= self.uiMsgs_.count) {
            continue;
        }
        TUIMessageCellData *data = self.uiMsgs_[[i intValue]];
        if (data.innerMessage.isSelf) {
            continue;
        }
        if (data.innerMessage == nil) {
            continue;
        }
        // Use Set to avoid sending duplicate element to SDK
        if (data.msgID.length > 0) {
            if ([self.sentReadGroupMsgSet containsObject:data.msgID]) {
                continue;
            } else {
                [self.sentReadGroupMsgSet addObject:data.msgID];
            }
        }
        // If needReadReceipt is NO, receiver won't send message read receipt
        if (!data.innerMessage.needReadReceipt) {
            continue;
        }
        [array addObject:data.innerMessage];
    }
    if (array.count == 0) {
        return;
    }
    [self.class sendMessageReadReceipts:array];
}

- (NSInteger)getIndexOfMessage:(NSString *)msgID {
    if (msgID.length == 0) {
        return -1;
    }
    for (int i = 0; i < self.uiMsgs.count; i++) {
        TUIMessageCellData *data = self.uiMsgs[i];
        if ([data.msgID isEqualToString:msgID]) {
            return i;
        }
    }
    return -1;
}

- (nullable TUIMessageCellData *)getSystemMsgFromDate:(NSDate *)date {
    if (self.msgForDate == nil || fabs([date timeIntervalSinceDate:self.msgForDate.timestamp]) > MaxDateMessageDelay) {
        TUIMessageCellData *system = [self.class getSystemMsgFromDate:date];
        return system;
    }
    return nil;
}

+ (void)updateUIMsgStatus:(TUIMessageCellData *)cellData uiMsgs:(NSArray *)uiMsgs {
    if (![uiMsgs containsObject:cellData]) {
        return;
    }
    NSInteger index = [uiMsgs indexOfObject:cellData];
    TUIMessageCellData *data = uiMsgs[index];

    TUIMessageCellData *lastData = nil;
    if (index >= 1) {
        lastData = uiMsgs[index - 1];
        if (![lastData isKindOfClass:[TUISystemMessageCellData class]]) {
            if ([lastData.identifier isEqualToString:data.identifier] &&
                ![data isKindOfClass:[TUISystemMessageCellData class]] &&(lastData.direction == data.direction)) {
                lastData.sameToNextMsgSender = YES;
                lastData.showAvatar = NO;
            } else {
                lastData.sameToNextMsgSender = NO;
                lastData.showAvatar = (lastData.direction == MsgDirectionIncoming ? YES : NO);
            }
        }
    }

    TUIMessageCellData *nextData = nil;
    if (index < uiMsgs.count - 1) {
        nextData = uiMsgs[index + 1];
        if ([data.identifier isEqualToString:nextData.identifier] &&(data.direction == nextData.direction)) {
            data.sameToNextMsgSender = YES;
            data.showAvatar = NO;
        } else {
            data.sameToNextMsgSender = NO;
            data.showAvatar = (data.direction == MsgDirectionIncoming ? YES : NO);
        }
    }

    if (index == uiMsgs.count - 1) {
        data.showAvatar = (data.direction == MsgDirectionIncoming ? YES : NO);
        data.sameToNextMsgSender = NO;
    }
}

- (void)getPinMessageList {
    if (self.conversationModel.groupID.length > 0) {
        __weak typeof(self) weakSelf = self;
        [V2TIMManager.sharedInstance getPinnedGroupMessageList:self.conversationModel.groupID succ:^(NSArray<V2TIMMessage *> *messageList) {
            weakSelf.groupPinList = [NSMutableArray arrayWithArray:messageList];
            if (weakSelf.pinGroupMessageChanged) {
                weakSelf.pinGroupMessageChanged(weakSelf.groupPinList);
            }
        } fail:^(int code, NSString *desc) {
            weakSelf.groupPinList = [NSMutableArray array];
            if (weakSelf.pinGroupMessageChanged) {
                weakSelf.pinGroupMessageChanged(weakSelf.groupPinList);
            }
        }];
    }

}
- (void)loadGroupInfo:(dispatch_block_t)callback {
    if (self.conversationModel.groupID.length == 0) {
        if (callback) {
            callback();
        }
        return;
    }
    __weak typeof(self) weakSelf = self;

    [[V2TIMManager sharedInstance] getGroupsInfo:@[ self.conversationModel.groupID ]
        succ:^(NSArray<V2TIMGroupInfoResult *> *groupResultList) {
            if (groupResultList.count == 1) {
                V2TIMGroupInfo *info = groupResultList[0].info;
                weakSelf.groupInfo = info;
            }
            if (callback) {
                callback();
            }
        }
        fail:^(int code, NSString *msg) {
          [TUITool makeToastError:code msg:msg];
        }];
}
- (void)getSelfInfoInGroup:(dispatch_block_t)callback {
    NSString *loginUserID = [[V2TIMManager sharedInstance] getLoginUser];
    if (loginUserID.length == 0) {
        if (callback) {
            callback();
        }
        return;
    }
    if (!self.conversationModel.enableRoom) {
        if (callback) {
            callback();
        }
        return;
    }
    __weak typeof(self) weakSelf = self;
    [[V2TIMManager sharedInstance] getGroupMembersInfo:self.conversationModel.groupID
        memberList:@[ loginUserID ]
        succ:^(NSArray<V2TIMGroupMemberFullInfo *> *memberList) {
          for (V2TIMGroupMemberFullInfo *item in memberList) {
              if ([item.userID isEqualToString:loginUserID]) {
                  weakSelf.groupSelfInfo = item;
                  if ([weakSelf.groupInfo.owner isEqualToString:loginUserID]) {
                      weakSelf.changedRole = V2TIM_GROUP_MEMBER_ROLE_SUPER;
                  }
                  else {
                      weakSelf.changedRole = item.role;
                  }
                  break;
              }
          }
          if (callback) {
              callback();
          }
        }
        fail:^(int code, NSString *desc) {
          [TUITool makeToastError:code msg:desc];
          if (callback) {
              callback();
          }
        }];
}

- (BOOL)isCurrentUserRoleSuperAdminInGroup {
    if (self.changedRole != V2TIM_GROUP_MEMBER_UNDEFINED) {
        return (self.changedRole == V2TIM_GROUP_MEMBER_ROLE_ADMIN || self.changedRole == V2TIM_GROUP_MEMBER_ROLE_SUPER);
    }
    return ([self.groupInfo.owner isEqualToString:[[V2TIMManager sharedInstance] getLoginUser]] ||self.groupSelfInfo.role == V2TIM_GROUP_MEMBER_ROLE_ADMIN || self.groupSelfInfo.role == V2TIM_GROUP_MEMBER_ROLE_SUPER) ;
}

- (BOOL)isCurrentMessagePin:(NSString *)msgID {
    BOOL isPin = NO;
    for (V2TIMMessage *msg in self.groupPinList) {
        if ([msg.msgID isEqualToString:msgID]) {
            isPin = YES;
            break;
        }
    }
    return isPin;
}

- (void)pinGroupMessage:(NSString *)groupID
                message:(V2TIMMessage *)message
               isPinned:(BOOL)isPinned
                   succ:(V2TIMSucc)succ
                   fail:(V2TIMFail)fail {
    __weak typeof(self) weakSelf = self;
    [V2TIMManager.sharedInstance pinGroupMessage:groupID message:message isPinned:isPinned succ:^{
        if (isPinned) {
            //del from changed

        }
        else {
            //add from changed
            
        }
        

        if (succ) {
            succ();
        }
    } fail:^(int code, NSString *desc) {
        if (fail) {
            fail(code,desc);
        }
    }];
}

- (void)onGroupMessagePinned:(NSString *)groupID message:(V2TIMMessage *)message isPinned:(BOOL)isPinned opUser:(V2TIMGroupMemberInfo *)opUser {
    if (![groupID isEqualToString:self.conversationModel.groupID]) {
        return;
    }
    if (isPinned) {
        //add
        [self.groupPinList addObject:message];
    }
    else {
        //del
        NSMutableArray *formatArray = [NSMutableArray arrayWithArray:self.groupPinList];
        for (V2TIMMessage *msg in formatArray) {
            if ([msg.msgID isEqualToString:message.msgID]) {
                [self.groupPinList removeObject:msg];
                break;
            }
        }

    }
    if (self.pinGroupMessageChanged) {
        self.pinGroupMessageChanged(self.groupPinList);
    }
}
- (void)onGroupInfoChanged:(NSString *)groupID changeInfoList:(NSArray<V2TIMGroupChangeInfo *> *)changeInfoList {
    if (![groupID isEqualToString:self.conversationModel.groupID]) {
        return;
    }
    
    for (V2TIMGroupChangeInfo *changeInfo in changeInfoList) {
        if (changeInfo.type == V2TIM_GROUP_INFO_CHANGE_TYPE_OWNER) {
            NSString *ownerID =  changeInfo.value;
           
            if ([ownerID isEqualToString:[TUILogin getUserID]]) {
                self.changedRole = V2TIM_GROUP_MEMBER_ROLE_SUPER;
            }
            else {
                if (self.changedRole ==V2TIM_GROUP_MEMBER_ROLE_SUPER) {
                    self.changedRole = V2TIM_GROUP_MEMBER_ROLE_MEMBER;
                }
            }
            return;
        }
    }
}
- (void)onGrantAdministrator:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser memberList:(NSArray <V2TIMGroupMemberInfo *> *)memberList {
    if (![groupID isEqualToString:self.conversationModel.groupID]) {
        return;
    }
    for (V2TIMGroupMemberInfo *changeInfo in memberList) {
        if (changeInfo.userID == [TUILogin getUserID]) {
            self.changedRole = V2TIM_GROUP_MEMBER_ROLE_ADMIN;
            return;
        }
    }
}

- (void)onRevokeAdministrator:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser memberList:(NSArray <V2TIMGroupMemberInfo *> *)memberList {
    if (![groupID isEqualToString:self.conversationModel.groupID]) {
        return;
    }
    for (V2TIMGroupMemberInfo *changeInfo in memberList) {
        if (changeInfo.userID == [TUILogin getUserID]) {
            self.changedRole = V2TIM_GROUP_MEMBER_ROLE_MEMBER;
            return;
        }
    }
}

@end

@implementation TUIMessageBaseDataProvider (IMSDK)

static const int kOfflinePushVersion = 1;

+ (NSString *)sendMessage:(V2TIMMessage *)message
           toConversation:(TUIChatConversationModel *)conversationData
             appendParams:(TUISendMessageAppendParams *)appendParams
                 Progress:(nullable V2TIMProgress)progress
                SuccBlock:(nullable V2TIMSucc)succ
                FailBlock:(nullable V2TIMFail)fail {
    NSString *userID = conversationData.userID;
    NSString *groupID = conversationData.groupID;
    NSAssert(userID || groupID, @"userID and groupID cannot be null at same time");
    NSString *conversationID = @"";
    if (!appendParams) {
        NSLog(@"appendParams cannot be nil");
    }
    BOOL isSendPushInfo = appendParams.isSendPushInfo;
    BOOL isOnlineUserOnly = appendParams.isOnlineUserOnly;
    V2TIMMessagePriority priority = appendParams.priority;
    if (IS_NOT_EMPTY_NSSTRING(userID)) {
        conversationID = [NSString stringWithFormat:@"c2c_%@", userID];
    }

    if (IS_NOT_EMPTY_NSSTRING(groupID)) {
        conversationID = [NSString stringWithFormat:@"group_%@", groupID];
    }

    if (IS_NOT_EMPTY_NSSTRING(conversationData.conversationID)) {
        conversationID = conversationData.conversationID;
    }

    NSParameterAssert(message);

    V2TIMOfflinePushInfo *pushInfo = nil;
    if (isSendPushInfo) {
        pushInfo = [[V2TIMOfflinePushInfo alloc] init];
        BOOL isGroup = groupID.length > 0;
        NSString *senderId = isGroup ? (groupID) : ([TUILogin getUserID]);
        senderId = senderId ?: @"";
        NSString *nickName = isGroup ? (conversationData.title) : ([TUILogin getNickName] ?: [TUILogin getUserID]);
        nickName = nickName ?: @"";
        NSString * content = [self getDisplayString:message] ?: @"";
        OfflinePushExtInfo *extInfo = [[OfflinePushExtInfo alloc] init];
        OfflinePushExtBusinessInfo * entity = extInfo.entity;
        entity.action = 1;
        entity.content = content;
        entity.sender = senderId;
        entity.nickname = nickName;
        entity.faceUrl = [TUILogin getFaceUrl] ?: @"";
        entity.chatType = [isGroup ? @(V2TIM_GROUP) : @(V2TIM_C2C) integerValue];
        entity.version = kOfflinePushVersion;
        pushInfo.ext = [extInfo toReportExtString];
        if (content.length > 0) {
            pushInfo.desc = content;
        }
        if (nickName.length > 0) {
            pushInfo.title = nickName;
        }
        pushInfo.AndroidOPPOChannelID = @"tuikit";
        pushInfo.AndroidSound = TUIConfig.defaultConfig.enableCustomRing ? @"private_ring" : nil;
        pushInfo.AndroidHuaWeiCategory = @"IM";
        pushInfo.AndroidVIVOCategory = @"IM";
        pushInfo.AndroidHonorImportance = @"NORMAL";
        pushInfo.iOSInterruptionLevel = @"time-sensitive";
        pushInfo.enableIOSBackgroundNotification = NO;
    }

    if ([self isGroupCommunity:conversationData.groupType groupID:conversationData.groupID] ||
        [self isGroupAVChatRoom:conversationData.groupType]) {
        message.needReadReceipt = NO;
    }

    // Hidden conversation evokes the chat page from the address book entry - to send a message, the hidden flag needs to be cleared
    if (conversationID.length > 0) {
        [V2TIMManager.sharedInstance markConversation:@[ conversationID ] markType:@(V2TIM_CONVERSATION_MARK_TYPE_HIDE) enableMark:NO succ:nil fail:nil];
    }

    if (conversationData.userID.length > 0) {
        // C2C
        NSDictionary *cloudCustomDataDic = @{
            @"needTyping" : @1,
            @"version" : @1,
        };
        [message setCloudCustomData:cloudCustomDataDic forType:messageFeature];
    }

    return [V2TIMManager.sharedInstance sendMessage:message
        receiver:userID
        groupID:groupID
        priority:priority
        onlineUserOnly:isOnlineUserOnly
        offlinePushInfo:pushInfo
        progress:progress
        succ:^{
          succ();
        }
        fail:^(int code, NSString *desc) {
          if (code == ERR_SDK_INTERFACE_NOT_SUPPORT) {
              [TUITool postUnsupportNotificationOfService:TUIKitLocalizableString(TUIKitErrorUnsupportIntefaceMessageRead)];
          }
          fail(code, desc);
        }];
}

- (void)getLastMessage:(BOOL)isFromLocal succ:(void (^)(V2TIMMessage *message))succ fail:(V2TIMFail)fail; {
    V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];
    if (self.conversationModel.userID.length > 0) {
        option.userID = self.conversationModel.userID;
    }
    if (self.conversationModel.groupID.length > 0) {
        option.groupID = self.conversationModel.groupID;
    }
    option.getType = isFromLocal ? V2TIM_GET_LOCAL_OLDER_MSG : V2TIM_GET_CLOUD_OLDER_MSG;
    option.lastMsg = nil;
    option.count = 1;
    [[V2TIMManager sharedInstance] getHistoryMessageList:option succ:^(NSArray<V2TIMMessage *> *msgs) {
        if (succ) {
            succ(msgs.count > 0 ? msgs.firstObject : nil);
        }
    } fail:^(int code, NSString *desc) {
        if (fail) {
            fail(code, desc);
        }
    }];
}

+ (BOOL)isGroupCommunity:(NSString *)groupType groupID:(NSString *)groupID {
    return [groupType isEqualToString:@"Community"] || [groupID startsWith:@"@TGS#_"];
}

+ (BOOL)isGroupAVChatRoom:(NSString *)groupType {
    return [groupType isEqualToString:@"AVChatRoom"];
}

+ (void)markC2CMessageAsRead:(NSString *)userID succ:(nullable V2TIMSucc)succ fail:(nullable V2TIMFail)fail {
    NSString * conversationID = [NSString stringWithFormat:@"c2c_%@",userID];
    [[V2TIMManager sharedInstance] cleanConversationUnreadMessageCount:conversationID cleanTimestamp:0 cleanSequence:0 succ:succ fail:fail];
}

+ (void)markGroupMessageAsRead:(NSString *)groupID succ:(nullable V2TIMSucc)succ fail:(nullable V2TIMFail)fail {
    NSString * conversationID = [NSString stringWithFormat:@"group_%@",groupID];
    [[V2TIMManager sharedInstance] cleanConversationUnreadMessageCount:conversationID cleanTimestamp:0 cleanSequence:0 succ:succ fail:fail];
}

+ (void)markConversationAsUndead:(NSArray<NSString *> *)conversationIDList enableMark:(BOOL)enableMark {
    [V2TIMManager.sharedInstance markConversation:conversationIDList markType:@(V2TIM_CONVERSATION_MARK_TYPE_UNREAD) enableMark:enableMark succ:nil fail:nil];
}

+ (void)revokeMessage:(V2TIMMessage *)msg succ:(nullable V2TIMSucc)succ fail:(nullable V2TIMFail)fail {
    [[V2TIMManager sharedInstance] revokeMessage:msg succ:succ fail:fail];
}

+ (void)deleteMessages:(NSArray<V2TIMMessage *> *)msgList succ:(nullable V2TIMSucc)succ fail:(nullable V2TIMFail)fail {
    [[V2TIMManager sharedInstance] deleteMessages:msgList succ:succ fail:fail];
}

+ (void)modifyMessage:(V2TIMMessage *)msg completion:(V2TIMMessageModifyCompletion)completion {
    [[V2TIMManager sharedInstance] modifyMessage:msg completion:completion];
}

+ (void)sendMessageReadReceipts:(NSArray *)msgs {
    [[V2TIMManager sharedInstance] sendMessageReadReceipts:msgs
        succ:^{
          NSLog(@"sendMessageReadReceipts succeed");
        }
        fail:^(int code, NSString *desc) {
          if (code == ERR_SDK_INTERFACE_NOT_SUPPORT) {
              [TUITool postUnsupportNotificationOfService:TUIKitLocalizableString(TUIKitErrorUnsupportIntefaceMessageRead)];
          }
        }];
}

+ (void)getReadMembersOfMessage:(V2TIMMessage *)msg
                         filter:(V2TIMGroupMessageReadMembersFilter)filter
                        nextSeq:(NSUInteger)nextSeq
                     completion:(void (^)(int code, NSString *desc, NSArray *members, NSUInteger nextSeq, BOOL isFinished))block {
    [[V2TIMManager sharedInstance] getGroupMessageReadMemberList:msg
        filter:filter
        nextSeq:nextSeq
        count:100
        succ:^(NSMutableArray<V2TIMGroupMemberInfo *> *members, uint64_t nextSeq, BOOL isFinished) {
          if (block) {
              block(0, nil, members, nextSeq, isFinished);
          }
        }
        fail:^(int code, NSString *desc) {
          if (block) {
              block(code, desc, nil, 0, NO);
          }
        }];
}

+ (void)getMessageReadReceipt:(NSArray *)messages succ:(nullable V2TIMMessageReadReceiptsSucc)succ fail:(nullable V2TIMFail)fail {
    if (messages.count == 0) {
        if (fail) {
            fail(-1, @"messages empty");
        }
        return;
    }
    [[V2TIMManager sharedInstance] getMessageReadReceipts:messages succ:succ fail:fail];
}

+ (TUIMessageCellData *__nullable)getCellData:(V2TIMMessage *)message {
    // subclass override required
    return nil;
}

+ (nullable TUIMessageCellData *)getSystemMsgFromDate:(NSDate *)date {
    // subclass override required
    return nil;
}

+ (TUIMessageCellData *)getRevokeCellData:(V2TIMMessage *)message {
    // subclass override required
    return nil;
}

+ (nullable NSString *)getDisplayString:(V2TIMMessage *)message {
    // subclass override required
    return nil;
}

+ (NSString *)getRevokeDispayString:(V2TIMMessage *)message {
    return [self getRevokeDispayString:message operateUser:nil reason:nil];
}

+ (NSString *)getRevokeDispayString:(V2TIMMessage *)message operateUser:(V2TIMUserFullInfo *)operateUser reason:(NSString *)reason {
    V2TIMUserFullInfo *revokerInfo = message.revokerInfo ? message.revokerInfo : operateUser;
    BOOL hasRiskContent = message.hasRiskContent;
    NSString *revoker = message.sender;
    NSString *messageSender = message.sender;
    if (revokerInfo) {
        revoker = revokerInfo.userID;
    }
    NSString *content = TIMCommonLocalizableString(TUIKitMessageTipsNormalRecallMessage);
    if ([revoker isEqualToString:messageSender]) {
        if (message.isSelf) {
            content = TIMCommonLocalizableString(TUIKitMessageTipsYouRecallMessage);
        } else {
            if (message.userID.length > 0) {
                // c2c
                content = TIMCommonLocalizableString(TUIKitMessageTipsOthersRecallMessage);
            } else if (message.groupID.length > 0) {
                NSString *userName = [self.class getShowName:message];
                content = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsRecallMessageFormat), userName];
            } else {
                // empty
            }
        }
    } else {
        NSString *userName = [self.class getShowName:message];
        if (revokerInfo) {
            userName = revokerInfo.showName;
        }
        content = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsRecallMessageFormat), userName];
    }
    return rtlString(content);
}

+ (NSString *)getGroupTipsDisplayString:(V2TIMMessage *)message {
    V2TIMGroupTipsElem *tips = message.groupTipsElem;
    NSString *opUser = [self getOpUserName:tips.opMember];
    NSMutableArray<NSString *> *userList = [self getUserNameList:tips.memberList];
    NSString *str = nil;
    switch (tips.type) {
        case V2TIM_GROUP_TIPS_TYPE_JOIN: {
            if (opUser.length > 0) {
                if ((userList.count == 0) || (userList.count == 1 && [opUser isEqualToString:userList.firstObject])) {
                    str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsJoinGroupFormat), opUser];
                } else {
                    NSString *users = [userList componentsJoinedByString:@"、"];
                    str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsInviteJoinGroupFormat), opUser, users];
                }
            }
        } break;
        case V2TIM_GROUP_TIPS_TYPE_INVITE: {
            if (userList.count > 0) {
                NSString *users = [userList componentsJoinedByString:@"、"];
                str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsInviteJoinGroupFormat), opUser, users];
            }
        } break;
        case V2TIM_GROUP_TIPS_TYPE_QUIT: {
            if (opUser.length > 0) {
                str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsLeaveGroupFormat), opUser];
            }
        } break;
        case V2TIM_GROUP_TIPS_TYPE_KICKED: {
            if (userList.count > 0) {
                NSString *users = [userList componentsJoinedByString:@"、"];
                str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsKickoffGroupFormat), opUser, users];
            }
        } break;
        case V2TIM_GROUP_TIPS_TYPE_SET_ADMIN: {
            if (userList.count > 0) {
                NSString *users = [userList componentsJoinedByString:@"、"];
                str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsSettAdminFormat), users];
            }
        } break;
        case V2TIM_GROUP_TIPS_TYPE_CANCEL_ADMIN: {
            if (userList.count > 0) {
                NSString *users = [userList componentsJoinedByString:@"、"];
                str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsCancelAdminFormat), users];
            }
        } break;
        case V2TIM_GROUP_TIPS_TYPE_GROUP_INFO_CHANGE: {
            str = [self opGroupInfoChagedFormatStr:opUser ofUserList:userList ofTips:tips];
            if (str.length > 0) {
                str = [str substringToIndex:str.length - 1];
            }
        } break;
        case V2TIM_GROUP_TIPS_TYPE_MEMBER_INFO_CHANGE: {
            for (V2TIMGroupChangeInfo *info in tips.memberChangeInfoList) {
                if ([info isKindOfClass:V2TIMGroupMemberChangeInfo.class]) {
                    NSString *userId = [(V2TIMGroupMemberChangeInfo *)info userID];
                    int32_t muteTime = [(V2TIMGroupMemberChangeInfo *)info muteTime];
                    NSString *myId = V2TIMManager.sharedInstance.getLoginUser;
                    NSString *showName = [self.class getUserName:tips with:userId];
                    str = [NSString stringWithFormat:@"%@ %@", [userId isEqualToString:myId] ? TIMCommonLocalizableString(You) : showName,
                                                     muteTime == 0 ? TIMCommonLocalizableString(TUIKitMessageTipsUnmute)
                                                                   : TIMCommonLocalizableString(TUIKitMessageTipsMute)];
                    break;
                }
            }
        } break;
        case V2TIM_GROUP_TIPS_TYPE_PINNED_MESSAGE_ADDED: {
            if (opUser.length > 0) {
                str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsGroupPinMessage), opUser];
            }
        }
            break;
        case V2TIM_GROUP_TIPS_TYPE_PINNED_MESSAGE_DELETED: {
            if (opUser.length > 0) {
                str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsGroupUnPinMessage), opUser];
            }
        }
            break;
        default:
            break;
    }
    return rtlString(str);
}

+ (V2TIMMessage *)getCustomMessageWithJsonData:(NSData *)data {
    return [[V2TIMManager sharedInstance] createCustomMessage:data];
}

+ (V2TIMMessage *)getCustomMessageWithJsonData:(NSData *)data desc:(NSString *)desc extension:(NSString *)extension {
    return [[V2TIMManager sharedInstance] createCustomMessage:data desc:desc extension:extension];
}

+ (NSString *)opGroupInfoChagedFormatStr:(NSString *)opUser ofUserList:(NSMutableArray<NSString *> *)userList ofTips:(V2TIMGroupTipsElem *)tips{
    NSString *str = nil;
    str = [NSString stringWithFormat:@"%@", opUser];
    for (V2TIMGroupChangeInfo *info in tips.groupChangeInfoList) {
        switch (info.type) {
            case V2TIM_GROUP_INFO_CHANGE_TYPE_NAME: {
                str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIkitMessageTipsEditGroupNameFormat), str, info.value];
            } break;
            case V2TIM_GROUP_INFO_CHANGE_TYPE_INTRODUCTION: {
                str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupIntroFormat), str, info.value];
            } break;
            case V2TIM_GROUP_INFO_CHANGE_TYPE_NOTIFICATION: {
                if (info.value.length) {
                    str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupAnnounceFormat), str, info.value];
                } else {
                    str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsDeleteGroupAnnounceFormat), str];
                }
            } break;
            case V2TIM_GROUP_INFO_CHANGE_TYPE_FACE: {
                str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupAvatarFormat), str];
            } break;
            case V2TIM_GROUP_INFO_CHANGE_TYPE_OWNER: {
                if (userList.count) {
                    str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupOwnerFormat), str, userList.firstObject];
                } else {
                    str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupOwnerFormat), str, info.value];
                }

            } break;
            case V2TIM_GROUP_INFO_CHANGE_TYPE_SHUT_UP_ALL: {
                if (info.boolValue) {
                    str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSetShutupAllFormat), opUser];
                } else {
                    str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitCancelShutupAllFormat), opUser];
                }
            } break;
            case V2TIM_GROUP_INFO_CHANGE_TYPE_GROUP_ADD_OPT: {
                uint32_t addOpt = info.intValue;
                NSString *addOptDesc = @"unknown";
                if (addOpt == V2TIM_GROUP_ADD_FORBID) {
                    addOptDesc = TIMCommonLocalizableString(TUIKitGroupProfileJoinDisable);
                } else if (addOpt == V2TIM_GROUP_ADD_AUTH) {
                    addOptDesc = TIMCommonLocalizableString(TUIKitGroupProfileAdminApprove);
                } else if (addOpt == V2TIM_GROUP_ADD_ANY) {
                    addOptDesc = TIMCommonLocalizableString(TUIKitGroupProfileAutoApproval);
                }
                str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupAddOptFormat), str, addOptDesc];
            } break;
            case V2TIM_GROUP_INFO_CHANGE_TYPE_GROUP_APPROVE_OPT: {
                uint32_t addOpt = info.intValue;
                NSString *addOptDesc = @"unknown";
                if (addOpt == V2TIM_GROUP_ADD_FORBID) {
                    addOptDesc = TIMCommonLocalizableString(TUIKitGroupProfileInviteDisable);
                } else if (addOpt == V2TIM_GROUP_ADD_AUTH) {
                    addOptDesc = TIMCommonLocalizableString(TUIKitGroupProfileAdminApprove);
                } else if (addOpt == V2TIM_GROUP_ADD_ANY) {
                    addOptDesc = TIMCommonLocalizableString(TUIKitGroupProfileAutoApproval);
                }
                str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupInviteOptFormat), str, addOptDesc];
            } break;
            default:
                break;
        }
    }
    return rtlString(str);
}
+ (NSString *)getOpUserName:(V2TIMGroupMemberInfo *)info {
    NSString *opUser;
    if (info.nameCard.length > 0) {
        opUser = info.nameCard;
    } else if (info.nickName.length > 0) {
        opUser = info.nickName;
    } else {
        opUser = info.userID;
    }
    return opUser;
}

+ (NSMutableArray *)getUserNameList:(NSArray<V2TIMGroupMemberInfo *> *)infoList {
    NSMutableArray<NSString *> *userNameList = [NSMutableArray array];
    for (V2TIMGroupMemberInfo *info in infoList) {
        if (info.nameCard.length > 0) {
            [userNameList addObject:info.nameCard];
        } else if (info.nickName.length > 0) {
            [userNameList addObject:info.nickName];
        } else {
            if (info.userID.length > 0) {
                [userNameList addObject:info.userID];
            }
        }
    }
    return userNameList;
}

+ (NSMutableArray *)getUserIDList:(NSArray<V2TIMGroupMemberInfo *> *)infoList {
    NSMutableArray<NSString *> *userIDList = [NSMutableArray array];
    for (V2TIMGroupMemberInfo *info in infoList) {
        if (info.userID.length > 0) {
            [userIDList addObject:info.userID];
        }
    }
    return userIDList;
}

+ (NSString *)getShowName:(V2TIMMessage *)message {
    NSString *showName = message.sender;
    if (message.nameCard.length > 0) {
        showName = message.nameCard;
    } else if (message.friendRemark.length > 0) {
        showName = message.friendRemark;
    } else if (message.nickName.length > 0) {
        showName = message.nickName;
    }
    return showName;
}

+ (NSString *)getUserName:(V2TIMGroupTipsElem *)tips with:(NSString *)userId {
    NSString *str = @"";
    for (V2TIMGroupMemberInfo *info in tips.memberList) {
        if ([info.userID isEqualToString:userId]) {
            if (info.nameCard.length > 0) {
                str = info.nameCard;
            } else if (info.friendRemark.length > 0) {
                str = info.friendRemark;
            } else if (info.nickName.length > 0) {
                str = info.nickName;
            } else {
                str = userId;
            }
            break;
        }
    }
    return str;
}

@end
