'use strict';

exports.LoadUtils = () => {
    window.WWebJS = {};

    const LazyLoadMap = {
        'WAWebSetPushnameConnAction': 'WAWebProfileDrawerLoadableRequireBundle',
        'WAWebChatForwardMessage': 'WAWebForwardMessageModalLoadable'
    };

    // Resolve modules that can be unavailable until WA Web lazy-loads their bundle.
    window.WWebJS.resolveModule = async (moduleName) => {
        let module = window.require(moduleName);
        if (module) return module;

        const bundleName = LazyLoadMap[moduleName];

        if (bundleName) {
            const bundle = window.require(bundleName);

            if (bundle && bundle.requireBundle) {
                console.warn(`Module '${moduleName}' not found, trying to require bundle: '${bundleName}'`);
                await bundle.requireBundle();
            }

            if (bundle && bundle.loadBundle) {
                console.warn(`Module '${moduleName}' not found, trying to load bundle: '${bundleName}'`);
                await bundle.loadBundle();
            }

            module = window.require(moduleName);
            if (module) return module;
        }

        throw new Error(`Module '${moduleName}' not found`);
    };

    window.WWebJS.setPushname = async (name) => {
        try {
            const module = await window.WWebJS.resolveModule('WAWebSetPushnameConnAction');
            return await module.setPushname(name);
        } catch (e) {
            console.error('Failed to load \'WAWebSetPushnameConnAction\'. setPushname is not available', e);
            return false;
        }
    };

    /**
     * Helper function that compares between two WWeb versions. Its purpose is to help the developer to choose the correct code implementation depending on the comparison value and the WWeb version.
     * @param {string} lOperand The left operand for the WWeb version string to compare with
     * @param {string} operator The comparison operator
     * @param {string} rOperand The right operand for the WWeb version string to compare with
     * @returns {boolean} Boolean value that indicates the result of the comparison
     */
    window.WWebJS.compareWwebVersions = (lOperand, operator, rOperand) => {
        if (!['>', '>=', '<', '<=', '='].includes(operator)) {
            throw new class _ extends Error {
                constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; }
            }('Invalid comparison operator is provided');

        }
        if (typeof lOperand !== 'string' || typeof rOperand !== 'string') {
            throw new class _ extends Error {
                constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; }
            }('A non-string WWeb version type is provided');
        }

        lOperand = lOperand.replace(/-beta$/, '');
        rOperand = rOperand.replace(/-beta$/, '');

        while (lOperand.length !== rOperand.length) {
            lOperand.length > rOperand.length
                ? rOperand = rOperand.concat('0')
                : lOperand = lOperand.concat('0');
        }

        lOperand = Number(lOperand.replace(/\./g, ''));
        rOperand = Number(rOperand.replace(/\./g, ''));

        return (
            operator === '>' ? lOperand > rOperand :
                operator === '>=' ? lOperand >= rOperand :
                    operator === '<' ? lOperand < rOperand :
                        operator === '<=' ? lOperand <= rOperand :
                            operator === '=' ? lOperand === rOperand :
                                false
        );
    };

    /**
     * Target options object description
     * @typedef {Object} TargetOptions
     * @property {string|number} module The target module
     * @property {string} function The function name to get from a module
     */
    /**
     * Function to modify functions
     * @param {TargetOptions} target Options specifying the target function to search for modifying
     * @param {Function} callback Modified function
     */
    window.WWebJS.injectToFunction = (target, callback) => {
        try {
            let module = window.require(target.module);
            if (!module) return; 

            const path = target.function.split('.');
            const funcName = path.pop();

            for (const key of path) {
                if (!module[key]) return;
                module = module[key];
            }

            const originalFunction = module[funcName];
            if (typeof originalFunction !== 'function') return;

            module[funcName] = ((...args) => {
                try {
                    return callback(module, originalFunction, ...args);
                } catch {
                    return originalFunction.apply(module, args);
                }
            }).bind(module);

        } catch {
            return;
        }
    };

    window.WWebJS.injectToFunction({ module: 'WAWebBackendJobsCommon', function: 'mediaTypeFromProtobuf' }, (module, func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); });

    window.WWebJS.injectToFunction({ module: 'WAWebE2EProtoUtils', function: 'typeAttributeFromProtobuf' }, (module, func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); });

    
    window.WWebJS.forwardMessage = async (chatId, msgId) => {
        const msg = (window.require('WAWebCollections')).Msg.get(msgId) || (await (window.require('WAWebCollections')).Msg.getMessagesById([msgId]))?.messages?.[0];
        const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
        const module = await window.WWebJS.resolveModule('WAWebChatForwardMessage');
        return await module.forwardMessages({'chat': chat, 'msgs' : [msg], 'multicast': true, 'includeCaption': true, 'appendedText' : undefined});
    };

    window.WWebJS.sendSeen = async (chatId) => {
        const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
        if (chat) {
            window.require('WAWebStreamModel').Stream.markAvailable();
            await (window.require('WAWebUpdateUnreadChatAction')).sendSeen({
                chat: chat,
                threadId: undefined
            });         
            window.require('WAWebStreamModel').Stream.markUnavailable();
            return true;
        }
        return false;
    };

    window.WWebJS.getUploadLimits = async (messageType) => {
        if (!window.WWebJS._uploadLimitResolver) {
            const resolveFromModule = (module) => {
                if (!module) return null;
                if (typeof module.getUploadLimit === 'function') return module.getUploadLimit.bind(module);
                if (module.default && typeof module.default.getUploadLimit === 'function') return module.default.getUploadLimit.bind(module.default);
                return null;
            };

            const moduleCandidates = [
                'WAWebMediaMmsV4Upload',
                'WAWebUploadManager',
                'WAWebMediaMmsV4UploadModule'
            ];

            for (const moduleName of moduleCandidates) {
                try {
                    const resolver = resolveFromModule(window.require(moduleName));
                    if (resolver) {
                        window.WWebJS._uploadLimitResolver = resolver;
                        break;
                    }
                } catch {
                    // Continue searching in other modules.
                }
            }
        }

        if (window.WWebJS._uploadLimitResolver) {
            return await window.WWebJS._uploadLimitResolver(messageType);
        }

        return Number.MAX_SAFE_INTEGER;
    };

    window.WWebJS.sendMessage = async (chat, content, options = {}) => {
        const { getIsNewsletter, getIsBroadcast } = window.require('WAWebChatGetters');
        const isChannel = getIsNewsletter(chat);
        const isStatus = getIsBroadcast(chat);

        const { findLink } = window.require('WALinkify');
        
        let mediaOptions = {};
        if (options.media) {
            mediaOptions = options.sendMediaAsSticker && !isChannel && !isStatus
                ? await window.WWebJS.processStickerData(options.media)
                : options.sendMediaAsStickerPack && !isChannel
                    ? await window.WWebJS.processStickerPackData(options.media, {
                        stickers: options.extraOptions.stickers,
                        stickerPackId: options.extraOptions.stickerPackId,
                        stickerPackName: options.extraOptions.stickerPackName,
                        stickerPackPublisher: options.extraOptions.stickerPackPublisher
                    })
                    : await window.WWebJS.processMediaData(options.media, {
                        forceSticker: options.sendMediaAsSticker,
                        forceGif: options.sendVideoAsGif,
                        forceVoice: options.sendAudioAsVoice,
                        forceDocument: options.sendMediaAsDocument,
                        forceMediaHd: options.sendMediaAsHd,
                        isViewOnce: options.isViewOnce,
                        sendToChannel: isChannel,
                        sendToStatus: isStatus
                    });
            mediaOptions.caption = options.caption;
            content = options.sendMediaAsSticker || options.sendMediaAsStickerPack ? undefined : mediaOptions.preview;
            mediaOptions.isViewOnce = options.isViewOnce;
            delete options.media;
            delete options.sendMediaAsSticker;
            delete options.sendMediaAsStickerPack;
        }

        let quotedMsgOptions = {};
        if (options.quotedMessageId) {
            let quotedMessage = (window.require('WAWebCollections')).Msg.get(options.quotedMessageId);
            !quotedMessage && (quotedMessage = (await (window.require('WAWebCollections')).Msg.getMessagesById([options.quotedMessageId]))?.messages?.[0]);
            if (quotedMessage) {

                const ReplyUtils = window.require('WAWebMsgReply');
                const canReply = ReplyUtils
                    ? ReplyUtils.canReplyMsg(quotedMessage.unsafe())
                    : quotedMessage.canReply();

                if (canReply) {
                    quotedMsgOptions = quotedMessage.msgContextInfo(chat.id || chat);
                }
            } else {
                if (!options.ignoreQuoteErrors) {
                    throw new Error('Could not get the quoted message.');
                }
            }
            
            delete options.ignoreQuoteErrors;
            delete options.quotedMessageId;
        }

        if (options.mentionedJidList) {
            options.mentionedJidList = options.mentionedJidList.map((id) => window.require('WAWebWidFactory').createWid(id));
            options.mentionedJidList = options.mentionedJidList.filter(Boolean);
        }

        if (options.groupMentions) {
            options.groupMentions = options.groupMentions.map((e) => ({
                groupSubject: e.subject,
                groupJid: window.require('WAWebWidFactory').createWid(e.id)
            }));
        }

        let locationOptions = {};
        if (options.location) {
            let { latitude, longitude, description, url } = options.location;
            url = findLink(url)?.href;
            url && !description && (description = url);
            locationOptions = {
                type: 'location',
                loc: description,
                lat: latitude,
                lng: longitude,
                clientUrl: url
            };
            delete options.location;
        }

        let pollOptions = {};
        if (options.poll) {
            const { pollName, pollOptions: _pollOptions } = options.poll;
            const { allowMultipleAnswers, messageSecret } = options.poll.options;
            pollOptions = {
                kind: 'pollCreation',
                type: 'poll_creation',
                pollName: pollName,
                pollOptions: _pollOptions,
                pollSelectableOptionsCount: allowMultipleAnswers ? 0 : 1,
                messageSecret:
                    Array.isArray(messageSecret) && messageSecret.length === 32
                        ? new Uint8Array(messageSecret)
                        : window.crypto.getRandomValues(new Uint8Array(32))
            };
            delete options.poll;
        }

        let eventOptions = {};
        if (options.event) {
            const { name, startTimeTs, eventSendOptions } = options.event;
            const { messageSecret } = eventSendOptions;
            eventOptions = {
                type: 'event_creation',
                eventName: name,
                eventDescription: eventSendOptions.description,
                eventStartTime: startTimeTs,
                eventEndTime: eventSendOptions.endTimeTs,
                eventLocation: eventSendOptions.location && {
                    degreesLatitude: 0,
                    degreesLongitude: 0,
                    name: eventSendOptions.location
                },
                eventJoinLink: eventSendOptions.callType === 'none' ? null : await (window.require('WAWebGenerateEventCallLink')).createEventCallLink(
                    startTimeTs,
                    eventSendOptions.callType
                ),
                isEventCanceled: eventSendOptions.isEventCanceled,
                messageSecret:
                    Array.isArray(messageSecret) && messageSecret.length === 32
                        ? new Uint8Array(messageSecret)
                        : window.crypto.getRandomValues(new Uint8Array(32)),
            };
            delete options.event;
        }

        let vcardOptions = {};
        if (options.contactCard) {
            let contact = (window.require('WAWebCollections')).Contact.get(options.contactCard);
            vcardOptions = {
                body: window.require('WAWebFrontendVcardUtils').vcardFromContactModel(contact).vcard,
                type: 'vcard',
                vcardFormattedName: contact.formattedName
            };
            delete options.contactCard;
        } else if (options.contactCardList) {
            let contacts = options.contactCardList.map(c => (window.require('WAWebCollections')).Contact.get(c));
            let vcards = contacts.map(c => window.require('WAWebFrontendVcardUtils').vcardFromContactModel(c));
            vcardOptions = {
                type: 'multi_vcard',
                vcardList: vcards,
                body: null
            };
            delete options.contactCardList;
        } else if (options.parseVCards && typeof (content) === 'string' && content.startsWith('BEGIN:VCARD')) {
            delete options.parseVCards;
            delete options.linkPreview;
            try {
                const parsed = window.require('WAWebVcardParsingUtils').parseVcard(content);
                if (parsed) {
                    vcardOptions = {
                        type: 'vcard',
                        vcardFormattedName: window.require('WAWebVcardGetNameFromParsed').vcardGetNameFromParsed(parsed)
                    };
                }
            } catch (_) {
                // not a vcard
            }
        }

        if (options.linkPreview) {
            delete options.linkPreview;
            const link = findLink(content);
            if (link) {
                let preview = await (window.require('WAWebLinkPreviewChatAction')).getLinkPreview(link);
                if (preview && preview.data) {
                    preview = preview.data;
                    preview.preview = true;
                    preview.subtype = 'url';
                    options = {...options, ...preview};
                }
            }
        }

        let buttonOptions = {};
        if (options.buttons) {
            let caption;
            if (options.buttons.type === 'chat') {
                content = options.buttons.body;
                caption = content;
            } else {
                caption = options.caption ? options.caption : ' '; //Caption can't be empty
            }
            buttonOptions = {
                productHeaderImageRejected: false,
                isFromTemplate: false,
                isDynamicReplyButtonsMsg: true,
                title: options.buttons.title ? options.buttons.title : undefined,
                footer: options.buttons.footer ? options.buttons.footer : undefined,
                dynamicReplyButtons: options.buttons.buttons,
                replyButtons: options.buttons.buttons,
                caption: caption
            };
            delete options.buttons;
        }

        let listOptions = {};
        if (options.list) {
            if ((window.require('WAWebConnModel').Conn).platform === 'smba' || (window.require('WAWebConnModel').Conn).platform === 'smbi') {
                throw '[LT01] Whatsapp business can\'t send this yet';
            }
            listOptions = {
                type: 'list',
                footer: options.list.footer,
                list: {
                    ...options.list,
                    listType: 1
                },
                body: options.list.description
            };
            delete options.list;
            delete listOptions.list.footer;
        }

        const botOptions = {};
        if (options.invokedBotWid) {
            botOptions.messageSecret = window.crypto.getRandomValues(new Uint8Array(32));
            botOptions.botMessageSecret = await (window.require('WAWebBotMessageSecret')).genBotMsgSecretFromMsgSecret(botOptions.messageSecret);
            botOptions.invokedBotWid = window.require('WAWebWidFactory').createWid(options.invokedBotWid);
            botOptions.botPersonaId = window.require('WAWebBotProfileCollection').BotProfileCollection.get(options.invokedBotWid).personaId;
            delete options.invokedBotWid;
        }
        const { getMaybeMeLidUser, getMaybeMePnUser } = window.require('WAWebUserPrefsMeUser');
        const lidUser = getMaybeMeLidUser();
        const meUser = getMaybeMePnUser();
        const newId = await (window.require('WAWebMsgKey')).newId();
        let from = chat.id.isLid() ? lidUser : meUser;
        let participant;

        if (typeof chat.id?.isGroup === 'function' && chat.id.isGroup()) {
            from = chat.groupMetadata && chat.groupMetadata.isLidAddressingMode ? lidUser : meUser;
            participant = window.require('WAWebWidFactory').asUserWidOrThrow(from);
        }

        if (typeof chat.id?.isStatus === 'function' && chat.id.isStatus()) {
            participant = window.require('WAWebWidFactory').asUserWidOrThrow(from);
        }

        const newMsgKey = new (window.require('WAWebMsgKey'))({
            from: from,
            to: chat.id,
            id: newId,
            participant: participant,
            selfDir: 'out',
        });

        const extraOptions = options.extraOptions || {};
        delete options.extraOptions;

        const ephemeralFields = window.require('WAWebGetEphemeralFieldsMsgActionsUtils').getEphemeralFields(chat);

        const message = {
            ...options,
            id: newMsgKey,
            ack: 0,
            body: content,
            from: from,
            to: chat.id,
            local: true,
            self: 'out',
            t: parseInt(new Date().getTime() / 1000),
            isNewMsg: true,
            type: 'chat',
            ...ephemeralFields,
            ...mediaOptions,
            ...(mediaOptions.toJSON ? mediaOptions.toJSON() : {}),
            ...quotedMsgOptions,
            ...locationOptions,
            ...pollOptions,
            ...eventOptions,
            ...vcardOptions,
            ...buttonOptions,
            ...listOptions,
            ...botOptions,
            ...extraOptions
        };
        
        // Bot's won't reply if canonicalUrl is set (linking)
        if (botOptions) {
            delete message.canonicalUrl;
        }

        if (isChannel) {
            const msg = new (window.require('WAWebCollections')).Msg.modelClass(message);
            const msgDataFromMsgModel = (window.require('WAWebMsgDataFromModel')).msgDataFromMsgModel(msg);
            const isMedia = Object.keys(mediaOptions).length > 0;
            await (window.require('WAWebNewsletterUpdateMsgsRecordsJob')).addNewsletterMsgsRecords([msgDataFromMsgModel]);
            chat.msgs.add(msg);
            chat.t = msg.t;

            const sendChannelMsgResponse = await (window.require('WAWebNewsletterSendMessageJob')).sendNewsletterMessageJob({
                msg: msg,
                type: message.type === 'chat' ? 'text' : isMedia ? 'media' : 'pollCreation',
                newsletterJid: chat.id.toJid(),
                ...(isMedia
                    ? {
                        mediaMetadata: msg.avParams(),
                        mediaHandle: isMedia ? mediaOptions.mediaHandle : null,
                    }
                    : {}
                )
            });

            if (sendChannelMsgResponse.success) {
                msg.t = sendChannelMsgResponse.ack.t;
                msg.serverId = sendChannelMsgResponse.serverId;
            }
            msg.updateAck(1, true);
            await (window.require('WAWebNewsletterUpdateMsgsRecordsJob')).updateNewsletterMsgRecord(msg);
            return msg;
        }

        if (isStatus) {
            const { backgroundColor, fontStyle } = extraOptions;
            const isMedia = Object.keys(mediaOptions).length > 0;
            const mediaUpdate = data => (window.require('WAWebMediaUpdateMsg'))(data, mediaOptions);
            const msg = new (window.require('WAWebCollections')).Msg.modelClass({
                ...message,
                author: participant ? participant : null,
                messageSecret: window.crypto.getRandomValues(new Uint8Array(32)),
                cannotBeRanked: (window.require('WAWebStatusGatingUtils')).canCheckStatusRankingPosterGating()
            });

            // for text only
            const statusOptions = {
                color: backgroundColor && window.WWebJS.assertColor(backgroundColor) || 0xff7acca5,
                font: fontStyle >= 0 && fontStyle <= 7 && fontStyle || 0,
                text: msg.body
            };

            await (window.require('WAWebSendStatusMsgAction'))[
                isMedia ?
                    'sendStatusMediaMsgAction' : 'sendStatusTextMsgAction'
            ](
                ...(
                    isMedia ?
                        [msg, mediaUpdate] : [statusOptions]
                )
            );

            return msg;
        }

        const [msgPromise, sendMsgResultPromise] = (window.require('WAWebSendMsgChatAction')).addAndSendMsgToChat(chat, message);
        await msgPromise;

        if (options.waitUntilMsgSent) await sendMsgResultPromise;

        return (window.require('WAWebCollections')).Msg.get(newMsgKey._serialized);
    };
	
    window.WWebJS.editMessage = async (msg, content, options = {}) => {
        const extraOptions = options.extraOptions || {};
        delete options.extraOptions;
        
        if (options.mentionedJidList) {
            options.mentionedJidList = options.mentionedJidList.map((id) => window.require('WAWebWidFactory').createWid(id));
            options.mentionedJidList = options.mentionedJidList.filter(Boolean);
        }

        if (options.groupMentions) {
            options.groupMentions = options.groupMentions.map((e) => ({
                groupSubject: e.subject,
                groupJid: window.require('WAWebWidFactory').createWid(e.id)
            }));
        }

        if (options.linkPreview) {
            const { findLink } = window.require('WALinkify');
            delete options.linkPreview;
            const link = findLink(content);
            if (link) {
                const preview = await (window.require('WAWebLinkPreviewChatAction')).getLinkPreview(link);
                preview.preview = true;
                preview.subtype = 'url';
                options = { ...options, ...preview };
            }
        }


        const internalOptions = {
            ...options,
            ...extraOptions
        };

        await (window.require('WAWebSendMessageEditAction')).sendMessageEdit(msg, content, internalOptions);
        return (window.require('WAWebCollections')).Msg.get(msg.id._serialized);
    };

    window.WWebJS.toStickerData = async (mediaInfo) => {
        if (mediaInfo.mimetype == 'image/webp') return mediaInfo;

        const file = window.WWebJS.mediaInfoToFile(mediaInfo);
        const webpSticker = await (window.require('WAWebImageUtils')).toWebpSticker(file);
        const webpBuffer = await webpSticker.arrayBuffer();
        const data = window.WWebJS.arrayBufferToBase64(webpBuffer);

        return {
            mimetype: 'image/webp',
            data
        };
    };

    window.WWebJS.processStickerData = async (mediaInfo) => {
        if (mediaInfo.mimetype !== 'image/webp') throw new Error('Invalid media type');

        const file = window.WWebJS.mediaInfoToFile(mediaInfo);
        let filehash = await window.WWebJS.getFileHash(file);
        let mediaKey = await window.WWebJS.generateHash(32);

        const controller = new AbortController();
        const uploadedInfo = await (window.require('WAWebUploadManager')).encryptAndUpload({
            blob: file,
            type: 'sticker',
            signal: controller.signal,
            mediaKey,
            uploadQpl: window.require('WAWebStartMediaUploadQpl').startMediaUploadQpl({
                entryPoint: 'MediaUpload'
            }),
        });

        const stickerInfo = {
            ...uploadedInfo,
            clientUrl: uploadedInfo.url,
            deprecatedMms3Url: uploadedInfo.url,
            uploadhash: uploadedInfo.encFilehash,
            size: file.size,
            type: 'sticker',
            filehash
        };

        return stickerInfo;
    };

    window.WWebJS.processStickerPackData = async (mediaInfo, { stickers, stickerPackId, stickerPackName = 'Sticker Pack', stickerPackPublisher = 'WWebJS' }) => {
        if (mediaInfo.mimetype !== 'application/zip') throw new Error('Invalid media type');

        const file = window.WWebJS.mediaInfoToFile(mediaInfo);
        const filehash = await window.WWebJS.getFileHash(file);
        const mediaKey = await window.WWebJS.generateHash(32);
        stickerPackId = stickerPackId || await window.WWebJS.generateHash(32);

        const uploadedInfo = await (window.require('WAWebUploadManager')).encryptAndUpload({
            blob: file,
            type: 'sticker-pack',
            signal: (new AbortController()).signal,
            mediaKey
        });

        return {
            ...uploadedInfo,
            size: file.size,
            type: 'sticker-pack',
            filename: stickerPackName,
            stickerPackId,
            stickerPackPublisher,
            stickers,
            trayIconFileName: 'thumbnail.png',
            filehash
        };
    };

    window.WWebJS.processMediaData = async (mediaInfo, { forceSticker, forceGif, forceVoice, forceDocument, forceMediaHd, isViewOnce, sendToChannel, sendToStatus }) => {
        const file = window.WWebJS.mediaInfoToFile(mediaInfo);
        const OpaqueData = window.require('WAWebMediaOpaqueData');
        const opaqueData = await OpaqueData.createFromData(file, mediaInfo.mimetype);
        const uploadOrigin = 2;
        const forwardedFromWeb = false;
        const mediaParams = {
            asSticker: forceSticker,
            asGif: forceGif,
            isPtt: forceVoice,
            asDocument: forceDocument
        };
      
        if (forceMediaHd && file.type.indexOf('image/') === 0) {
            mediaParams.maxDimension = 2560;
        }
      
        const mediaPrep = window.require('WAWebPrepRawMedia').prepRawMedia(opaqueData, mediaParams);
        const mediaData = await mediaPrep.waitForPrep();
        const mediaObject = window.require('WAWebMediaStorage').getOrCreateMediaObject(mediaData.filehash);
        const mediaType = window.require('WAWebMmsMediaTypes').msgToMediaType({
            type: mediaData.type,
            isGif: mediaData.isGif,
            isNewsletter: sendToChannel,
        });
        const maxFileSize = await window.WWebJS.getUploadLimits(mediaData.type);

        if (!mediaData.filehash) {
            throw new Error('media-fault: sendToChat filehash undefined');
        }

        if (mediaInfo.filesize && mediaInfo.filesize > maxFileSize) {
            throw new Error('Media size (' + mediaInfo.filesize + ') exceeds current upload limit (' + maxFileSize + ')');
        }

        if (!isViewOnce) {
            isViewOnce = false;
        }

        if (
            mediaData.type === 'ptt'
            || (sendToStatus && mediaData.type === 'audio')
            || (mediaData.type === 'audio' && mediaData.mimetype === 'audio/ogg; codecs=opus' && mediaInfo.filesize < maxFileSize)
        ) {
            const waveform = mediaObject.contentInfo.waveform;
            mediaData.waveform =
                waveform || await window.WWebJS.generateWaveform(file);
        }

        if (!(mediaData.mediaBlob instanceof OpaqueData)) {
            mediaData.mediaBlob = await OpaqueData.createFromData(
                mediaData.mediaBlob,
                mediaData.mediaBlob.type
            );
        }

        mediaData.renderableUrl = mediaData.mediaBlob.url();
        mediaObject.consolidate(mediaData.toJSON());
        
        mediaData.mediaBlob.autorelease();
        const shouldUseMediaCache = (window.require('WAWebMediaDataUtils')).shouldUseMediaCache(
            window.require('WAWebMmsMediaTypes').castToV4(mediaObject.type)
        );
        if (shouldUseMediaCache && mediaData.mediaBlob instanceof OpaqueData) {
            const formData = mediaData.mediaBlob.formData();
            (window.require('WAWebMediaInMemoryBlobCache')).InMemoryMediaBlobCache.put(mediaObject.filehash, formData);
        }

        const dataToUpload = {
            mimetype: mediaData.mimetype,
            mediaObject,
            mediaType,
            isViewOnce,
            uploadOrigin,
            forwardedFromWeb,
            ...(sendToChannel ? { calculateToken: window.require('WAMediaCalculateFilehash').getRandomFilehash } : {})
        };

        if (!sendToChannel && mediaInfo.filesize > 50000000) {
            dataToUpload.mediaKeyInfo = {
                key: await window.WWebJS.generateHash(32),
                timestamp: Date.now()
            };
        }

        const { uploadMedia, uploadUnencryptedMedia } = window.require('WAWebMediaMmsV4Upload');
        let uploadedMedia = !sendToChannel
            ? await uploadMedia(dataToUpload)
            : await uploadUnencryptedMedia(dataToUpload);

        let mediaEntry = uploadedMedia.mediaEntry;
        if (!mediaEntry && !sendToChannel && mediaInfo.filesize > 50000000) {
            delete dataToUpload.mediaKeyInfo;
            uploadedMedia = await uploadMedia(dataToUpload);
            mediaEntry = uploadedMedia.mediaEntry;
        }

        if (!mediaEntry) {
            throw new Error('upload failed: media entry was not created');
        }

        mediaData.set({
            clientUrl: mediaEntry.mmsUrl,
            deprecatedMms3Url: mediaEntry.deprecatedMms3Url,
            directPath: mediaEntry.directPath,
            mediaKey: mediaEntry.mediaKey,
            mediaKeyTimestamp: mediaEntry.mediaKeyTimestamp,
            filehash: mediaObject.filehash,
            encFilehash: mediaEntry.encFilehash,
            uploadhash: mediaEntry.uploadHash,
            size: mediaObject.size,
            streamingSidecar: mediaEntry.sidecar,
            firstFrameSidecar: mediaEntry.firstFrameSidecar,
            mediaHandle: sendToChannel ? mediaEntry.handle : null,
        });

        return mediaData;
    };

    window.WWebJS.getPreferredRemoteJid = (widLike) => {
        if (!widLike) return null;

        const serializeWid = (wid) => {
            if (!wid) return null;
            if (typeof wid === 'string') return wid;
            if (typeof wid === 'object' && wid._serialized) return wid._serialized;
            if (typeof wid.toString === 'function') return wid.toString();
            return null;
        };

        let wid = widLike;
        if (typeof wid === 'string') {
            try {
                wid = window.require('WAWebWidFactory').createWid(wid);
            } catch {
                wid = widLike;
            }
        }

        if (wid && typeof wid === 'object' && wid.server === 'lid') {
            let phoneWid = null;
            try {
                const apiContact = window.require('WAWebApiContact');
                phoneWid = apiContact?.getPhoneNumber ? apiContact.getPhoneNumber(wid) : null;
            } catch {
                phoneWid = null;
            }
            if (phoneWid) {
                wid = phoneWid;
            }
        }

        let serialized = serializeWid(wid);
        if (!serialized) return null;

        const server = typeof wid === 'object' && wid?.server
            ? wid.server
            : serialized.split('@')[1] || '';

        if (server === 'c.us' || server === 'lid' || server === 's.whatsapp.net') {
            try {
                const userWid = typeof wid === 'object' && wid?._serialized
                    ? wid
                    : window.require('WAWebWidFactory').createWid(serialized);
                const userJid = window.require('WAWebWidToJid')?.widToUserJid?.(userWid);
                if (userJid?._serialized) {
                    serialized = userJid._serialized;
                }
            } catch {
                // Keep the best serialized fallback we already have.
            }
        }

        return serialized;
    };

    window.WWebJS.getMessageModel = (message) => {
        const msg = message.serialize();

        const { findLinks } = window.require('WALinkify');
        
        msg.isEphemeral = message.isEphemeral;
        msg.isStatusV3 = message.isStatusV3;
        msg.links = findLinks(message.mediaObject ? message.caption : message.body).map((link) => ({
            link: link.href,
            isSuspicious: Boolean(link.suspiciousCharacters && link.suspiciousCharacters.size)
        }));

        if (msg.buttons) {
            msg.buttons = msg.buttons.serialize();
        }
        if (msg.dynamicReplyButtons) {
            msg.dynamicReplyButtons = JSON.parse(JSON.stringify(msg.dynamicReplyButtons));
        }
        if (msg.replyButtons) {
            msg.replyButtons = JSON.parse(JSON.stringify(msg.replyButtons));
        }

        if (msg.id) {
            const remote = typeof msg.id.remote === 'object' ? msg.id.remote._serialized : msg.id.remote;
            const remoteJid = window.WWebJS.getPreferredRemoteJid(msg.id.remote || remote) || remote;
            msg.id = Object.assign({}, msg.id, { remote, remoteJid });
        }

        delete msg.pendingAckUpdate;

        return msg;
    };

    window.WWebJS.getChat = async (chatId, { getAsModel = true } = {}) => {
        const isChannel = /@\w*newsletter\b/.test(chatId);
        const chatWid = window.require('WAWebWidFactory').createWid(chatId);
        let chat;

        if (isChannel) {
            try {
                chat = (window.require('WAWebCollections')).WAWebNewsletterCollection.get(chatId);
                if (!chat) {
                    await (window.require('WAWebLoadNewsletterPreviewChatAction')).loadNewsletterPreviewChat(chatId);
                    chat = await (window.require('WAWebCollections')).WAWebNewsletterCollection.find(chatWid);
                }
            } catch (err) {
                chat = null;
            }
        } else {
            chat = (window.require('WAWebCollections')).Chat.get(chatWid) || (await (window.require('WAWebFindChatAction')).findOrCreateLatestChat(chatWid))?.chat;
        }

        return getAsModel && chat
            ? await window.WWebJS.getChatModel(chat, { isChannel: isChannel })
            : chat;
    };

    window.WWebJS.getChannelMetadata = async (inviteCode) => {
        const role = (window.require('WAWebNewsletterModelUtils')).getRoleByIdentifier(inviteCode);
        const response =
            await (window.require('WAWebNewsletterMetadataQueryJob')).queryNewsletterMetadataByInviteCode(
                inviteCode,
                role
            );

        const picUrl = response.newsletterPictureMetadataMixin?.picture[0]?.queryPictureDirectPathOrEmptyResponseMixinGroup.value.directPath;

        return {
            id: response.idJid,
            createdAtTs: response.newsletterCreationTimeMetadataMixin.creationTimeValue,
            titleMetadata: {
                title: response.newsletterNameMetadataMixin.nameElementValue,
                updatedAtTs: response.newsletterNameMetadataMixin.nameUpdateTime
            },
            descriptionMetadata: {
                description: response.newsletterDescriptionMetadataMixin.descriptionQueryDescriptionResponseMixin.elementValue,
                updatedAtTs: response.newsletterDescriptionMetadataMixin.descriptionQueryDescriptionResponseMixin.updateTime
            },
            inviteLink: `https://whatsapp.com/channel/${response.newsletterInviteLinkMetadataMixin.inviteCode}`,
            membershipType: role,
            stateType: response.newsletterStateMetadataMixin.stateType,
            pictureUrl: picUrl ? `https://pps.whatsapp.net${picUrl}` : null,
            subscribersCount: response.newsletterSubscribersMetadataMixin.subscribersCount,
            isVerified: response.newsletterVerificationMetadataMixin.verificationState === 'verified'
        };
    };

    window.WWebJS.getChats = async (options = {}) => {
        const { since } = options;

        const allChats = (window.require('WAWebCollections')).Chat.getModelsArray();

        const isValidSince = Number.isFinite(since) && since > 0;

        const filteredChats = isValidSince
            ? allChats.filter(chat => chat.t >= since)
            : allChats;

        return await Promise.all(
            filteredChats.map(chat => window.WWebJS.getChatModel(chat))
        );
    };

    window.WWebJS.getChannels = async () => {
        const channels = (window.require('WAWebCollections')).WAWebNewsletterCollection.getModelsArray();
        const channelPromises = channels?.map((channel) => window.WWebJS.getChatModel(channel, { isChannel: true }));
        return await Promise.all(channelPromises);
    };

    window.WWebJS.getChatModel = async (chat, { isChannel = false } = {}) => {
        if (!chat) return null;

        const model = chat.serialize();
        model.isGroup = false;
        model.isMuted = chat.mute?.expiration !== 0;
        if (isChannel) {
            model.isChannel = (window.require('WAWebChatGetters')).getIsNewsletter(chat);
        } else {
            model.formattedTitle = chat.formattedTitle;
        }

        if (chat.groupMetadata) {
            model.isGroup = true;
            const chatWid = window.require('WAWebWidFactory').createWid(chat.id._serialized);
            const groupMetadata = (window.require('WAWebCollections')).GroupMetadata || (window.require('WAWebCollections')).WAWebGroupMetadataCollection;
            await groupMetadata.update(chatWid);
            chat.groupMetadata.participants._models
                .filter(x => x.id?._serialized?.endsWith('@lid'))
                .forEach(x => x.contact?.phoneNumber && (x.id = x.contact.phoneNumber));
            model.groupMetadata = chat.groupMetadata.serialize();
            model.isReadOnly = chat.groupMetadata.announce;
        }

        if (chat.newsletterMetadata) {
            const newsletterMetadata = (window.require('WAWebCollections')).NewsletterMetadataCollection || (window.require('WAWebCollections')).WAWebNewsletterMetadataCollection;
            await newsletterMetadata.update(chat.id);
            model.channelMetadata = chat.newsletterMetadata.serialize();
            model.channelMetadata.createdAtTs = chat.newsletterMetadata.creationTime;
        }

        model.lastMessage = null;
        if (model.msgs && model.msgs.length) {
            const lastMessage = chat.lastReceivedKey
                ? (window.require('WAWebCollections')).Msg.get(chat.lastReceivedKey._serialized) || (await (window.require('WAWebCollections')).Msg.getMessagesById([chat.lastReceivedKey._serialized]))?.messages?.[0]
                : null;
            lastMessage && (model.lastMessage = window.WWebJS.getMessageModel(lastMessage));
        }

        delete model.msgs;
        delete model.msgUnsyncedButtonReplyMsgs;
        delete model.unsyncedButtonReplies;

        return model;
    };

    window.WWebJS.getContactModel = contact => {
        let res = contact.serialize();
        res.isBusiness = contact.isBusiness === undefined ? false : contact.isBusiness;

        if (contact.businessProfile) {
            res.businessProfile = contact.businessProfile.serialize();
        }
        
        res.isBlocked = contact.isContactBlocked;
        
        const ContactMethods = window.require('WAWebContactGetters');
        res.isMe = ContactMethods.getIsMe(contact);
        res.isUser = ContactMethods.getIsUser(contact);
        res.isGroup = ContactMethods.getIsGroup(contact);
        res.isWAContact = ContactMethods.getIsWAContact(contact);
        res.userid = ContactMethods.getUserid(contact);
        res.verifiedName = ContactMethods.getVerifiedName(contact);
        res.verifiedLevel = ContactMethods.getVerifiedLevel(contact);
        res.statusMute = ContactMethods.getStatusMute(contact);
        res.name = ContactMethods.getName(contact);
        res.shortName = ContactMethods.getShortName(contact);
        res.pushname = ContactMethods.getPushname(contact);
        
        const { getIsMyContact } = window.require('WAWebFrontendContactGetters');
        res.isMyContact = getIsMyContact(contact);
        res.isEnterprise = ContactMethods.getIsEnterprise(contact);

        return res;
    };

    window.WWebJS.getContact = async contactId => {
        const wid = window.require('WAWebWidFactory').createWid(contactId);
        let contact = await (window.require('WAWebCollections')).Contact.find(wid);
        if (contact.id._serialized.endsWith('@lid')) {
            contact.id = contact.phoneNumber;
        }
        const bizProfile = await (window.require('WAWebCollections')).BusinessProfile.fetchBizProfile(wid);
        bizProfile.profileOptions && (contact.businessProfile = bizProfile);
        return window.WWebJS.getContactModel(contact);
    };

    window.WWebJS.getContacts = () => {
        const contacts = (window.require('WAWebCollections')).Contact.getModelsArray();
        return contacts.map(contact => window.WWebJS.getContactModel(contact));
    };

    window.WWebJS.mediaInfoToFile = ({ data, mimetype, filename }) => {
        const binaryData = window.atob(data);

        const buffer = new ArrayBuffer(binaryData.length);
        const view = new Uint8Array(buffer);
        for (let i = 0; i < binaryData.length; i++) {
            view[i] = binaryData.charCodeAt(i);
        }

        const blob = new Blob([buffer], { type: mimetype });
        return new File([blob], filename, {
            type: mimetype,
            lastModified: Date.now()
        });
    };

    window.WWebJS.arrayBufferToBase64 = (arrayBuffer) => {
        let binary = '';
        const bytes = new Uint8Array(arrayBuffer);
        const len = bytes.byteLength;
        for (let i = 0; i < len; i++) {
            binary += String.fromCharCode(bytes[i]);
        }
        return window.btoa(binary);
    };

    window.WWebJS.arrayBufferToBase64Async = (arrayBuffer) =>
        new Promise((resolve, reject) => {
            const blob = new Blob([arrayBuffer], {
                type: 'application/octet-stream',
            });
            const fileReader = new FileReader();
            fileReader.onload = () => {
                const [, data] = fileReader.result.split(',');
                resolve(data);
            };
            fileReader.onerror = (e) => reject(e);
            fileReader.readAsDataURL(blob);
        });

    window.WWebJS.getFileHash = async (data) => {
        let buffer = await data.arrayBuffer();
        const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
        return btoa(String.fromCharCode(...new Uint8Array(hashBuffer)));
    };

    window.WWebJS.generateHash = async (length) => {
        var result = '';
        var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        var charactersLength = characters.length;
        for (var i = 0; i < length; i++) {
            result += characters.charAt(Math.floor(Math.random() * charactersLength));
        }
        return result;
    };

    window.WWebJS.generateWaveform = async (audioFile) => {
        try {
            const audioData = await audioFile.arrayBuffer();
            const audioContext = new AudioContext();
            const audioBuffer = await audioContext.decodeAudioData(audioData);

            const rawData = audioBuffer.getChannelData(0);
            const samples = 64;
            const blockSize = Math.floor(rawData.length / samples);
            const filteredData = [];
            for (let i = 0; i < samples; i++) {
                const blockStart = blockSize * i;
                let sum = 0;
                for (let j = 0; j < blockSize; j++) {
                    sum = sum + Math.abs(rawData[blockStart + j]);
                }
                filteredData.push(sum / blockSize);
            }

            const multiplier = Math.pow(Math.max(...filteredData), -1);
            const normalizedData = filteredData.map((n) => n * multiplier);

            const waveform = new Uint8Array(
                normalizedData.map((n) => Math.floor(100 * n))
            );

            return waveform;
        } catch (e) {
            return undefined;
        }
    };

    window.WWebJS.sendClearChat = async (chatId) => {
        let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
        if (chat !== undefined) {
            await (window.require('WAWebChatClearBridge')).sendClear(chat, false);
            return true;
        }
        return false;
    };

    window.WWebJS.sendDeleteChat = async (chatId) => {
        let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
        if (chat !== undefined) {
            await (window.require('WAWebDeleteChatAction')).sendDelete(chat);
            return true;
        }
        return false;
    };

    window.WWebJS.sendChatstate = async (state, chatId) => {
        chatId = window.require('WAWebWidFactory').createWid(chatId);

        const ChatState = window.require('WAWebChatStateBridge');
        switch (state) {
        case 'typing':
            await ChatState.sendChatStateComposing(chatId);
            break;
        case 'recording':
            await ChatState.sendChatStateRecording(chatId);
            break;
        case 'stop':
            await ChatState.sendChatStatePaused(chatId);
            break;
        default:
            throw 'Invalid chatstate';
        }

        return true;
    };

    window.WWebJS.getLabelModel = label => {
        let res = label.serialize();
        res.hexColor = label.hexColor;

        return res;
    };

    window.WWebJS.getLabels = () => {
        const labels = (window.require('WAWebCollections')).Label.getModelsArray();
        return labels.map(label => window.WWebJS.getLabelModel(label));
    };

    window.WWebJS.getLabel = (labelId) => {
        const label = (window.require('WAWebCollections')).Label.get(labelId);
        return window.WWebJS.getLabelModel(label);
    };

    window.WWebJS.getChatLabels = async (chatId) => {
        const chat = await window.WWebJS.getChat(chatId);
        return (chat.labels || []).map(id => window.WWebJS.getLabel(id));
    };

    window.WWebJS.getOrderDetail = async (orderId, token, chatId) => {
        const chatWid = window.require('WAWebWidFactory').createWid(chatId);
        return (window.require('WAWebBizOrderBridge')).queryOrder(chatWid, orderId, 80, 80, token);
    };

    window.WWebJS.getProductMetadata = async (productId) => {
        let sellerId = (window.require('WAWebConnModel').Conn).wid;
        let product = await (window.require('WAWebBizProductCatalogBridge')).queryProduct(sellerId, productId);
        if (product && product.data) {
            return product.data;
        }

        return undefined;
    };

    window.WWebJS.rejectCall = async (peerJid, id) => {
        let userId = (window.require('WAWebUserPrefsMeUser')).getMaybeMePnUser()._serialized;

        const stanza = window.require('WAWap').wap('call', {
            id: window.require('WAWap').generateId(),
            from: userId,
            to: peerJid,
        }, [
            window.require('WAWap').wap('reject', {
                'call-id': id,
                'call-creator': peerJid,
                count: '0',
            })
        ]);
        await (window.require('WADeprecatedSendIq')).deprecatedCastStanza(stanza);
    };
    
    window.WWebJS.cropAndResizeImage = async (media, options = {}) => {
        if (!media.mimetype.includes('image'))
            throw new Error('Media is not an image');

        if (options.mimetype && !options.mimetype.includes('image'))
            delete options.mimetype;

        options = Object.assign({ size: 640, mimetype: media.mimetype, quality: .75, asDataUrl: false }, options);

        const img = await new Promise ((resolve, reject) => {
            const img = new Image();
            img.onload = () => resolve(img);
            img.onerror = reject;
            img.src = `data:${media.mimetype};base64,${media.data}`;
        });

        const sl = Math.min(img.width, img.height);
        const sx = Math.floor((img.width - sl) / 2);
        const sy = Math.floor((img.height - sl) / 2);

        const canvas = document.createElement('canvas');
        canvas.width = options.size;
        canvas.height = options.size;

        const ctx = canvas.getContext('2d');
        ctx.drawImage(img, sx, sy, sl, sl, 0, 0, options.size, options.size);

        const dataUrl = canvas.toDataURL(options.mimetype, options.quality);

        if (options.asDataUrl)
            return dataUrl;

        return Object.assign(media, {
            mimetype: options.mimetype,
            data: dataUrl.replace(`data:${options.mimetype};base64,`, '')
        });
    };

    window.WWebJS.setPicture = async (chatId, media) => {
        const thumbnail = await window.WWebJS.cropAndResizeImage(media, { asDataUrl: true, mimetype: 'image/jpeg', size: 96 });
        const profilePic = await window.WWebJS.cropAndResizeImage(media, { asDataUrl: true, mimetype: 'image/jpeg', size: 640 });

        const chatWid = window.require('WAWebWidFactory').createWid(chatId);
        try {
            const collection = (window.require('WAWebCollections')).ProfilePicThumb.get(chatId) || await (window.require('WAWebCollections')).ProfilePicThumb.find(chatId);
            if (!collection?.canSet()) return false;

            const res = await (window.require('WAWebContactProfilePicThumbBridge')).sendSetPicture(chatWid, thumbnail, profilePic);
            return res ? res.status === 200 : false;
        } catch (err) {
            if (err.name === 'ServerStatusCodeError') return false;
            throw err;
        }
    };

    window.WWebJS.deletePicture = async (chatid) => {
        const chatWid = window.require('WAWebWidFactory').createWid(chatid);
        try {
            const collection = (window.require('WAWebCollections')).ProfilePicThumb.get(chatid);
            if (!collection.canDelete()) return;

            const res = await (window.require('WAWebContactProfilePicThumbBridge')).requestDeletePicture(chatWid);
            return res ? res.status === 200 : false;
        } catch (err) {
            if(err.name === 'ServerStatusCodeError') return false;
            throw err;
        }
    };
    
    window.WWebJS.getProfilePicThumbToBase64 = async (chatWid) => {
        const profilePicCollection = await (window.require('WAWebCollections')).ProfilePicThumb.find(chatWid);

        const _readImageAsBase64 = (imageBlob) => {
            return new Promise((resolve) => {
                const reader = new FileReader();
                reader.onloadend = function () {
                    const base64Image = reader.result;
                    if (base64Image == null) {
                        resolve(undefined);
                    } else {
                        const base64Data = base64Image.toString().split(',')[1];
                        resolve(base64Data);
                    }
                };
                reader.readAsDataURL(imageBlob);
            });
        };

        if (profilePicCollection?.img) {
            try {
                const response = await fetch(profilePicCollection.img);
                if (response.ok) {
                    const imageBlob = await response.blob();
                    if (imageBlob) {
                        const base64Image = await _readImageAsBase64(imageBlob);
                        return base64Image;
                    }
                }
            } catch (error) { /* empty */ }
        }
        return undefined;
    };

    window.WWebJS.getAddParticipantsRpcResult = async (groupWid, participantWid) => {
        const iqTo = window.require('WAWebWidToJid').widToGroupJid(groupWid);

        const participantArgs = [{
            participantJid: window.require('WAWebWidToJid').widToUserJid(participantWid)
        }];

        let rpcResult, resultArgs;
        const data = {
            name: undefined,
            code: undefined,
            inviteV4Code: undefined,
            inviteV4CodeExp: undefined
        };

        try {
            rpcResult = await (window.require('WASmaxGroupsAddParticipantsRPC')).sendAddParticipantsRPC({ participantArgs, iqTo });
            resultArgs = rpcResult.value.addParticipant[0]
                .addParticipantsParticipantAddedOrNonRegisteredWaUserParticipantErrorLidResponseMixinGroup
                .value
                .addParticipantsParticipantMixins;
        } catch (err) {
            data.code = 400;
            return data;
        }

        if (rpcResult.name === 'AddParticipantsResponseSuccess') {
            const code = resultArgs?.value.error || '200';
            data.name = resultArgs?.name;
            data.code = +code;
            data.inviteV4Code = resultArgs?.value.addRequestCode;
            data.inviteV4CodeExp = resultArgs?.value.addRequestExpiration?.toString();
        }

        else if (rpcResult.name === 'AddParticipantsResponseClientError') {
            const { code: code } = rpcResult.value.errorAddParticipantsClientErrors.value;
            data.code = +code;
        }

        else if (rpcResult.name === 'AddParticipantsResponseServerError') {
            const { code: code } = rpcResult.value.errorServerErrors.value;
            data.code = +code;
        }

        return data;
    };

    window.WWebJS.membershipRequestAction = async (groupId, action, requesterIds, sleep) => {
        const groupWid = window.require('WAWebWidFactory').createWid(groupId);
        const group = await (window.require('WAWebCollections')).Chat.find(groupWid);
        const toApprove = action === 'Approve';
        let membershipRequests;
        let response;
        let result = [];

        await (window.require('WAWebGroupQueryJob')).queryAndUpdateGroupMetadataById({ id: groupId });

        if (!requesterIds?.length) {
            membershipRequests = group.groupMetadata.membershipApprovalRequests._models.map(({ id }) => id);
        } else {
            !Array.isArray(requesterIds) && (requesterIds = [requesterIds]);
            membershipRequests = requesterIds.map(r => window.require('WAWebWidFactory').createWid(r));
        }

        if (!membershipRequests.length) return [];

        const participantArgs = membershipRequests.map(m => ({
            participantArgs: [
                {
                    participantJid: window.require('WAWebWidToJid').widToUserJid(m)
                }
            ]
        }));

        const groupJid = window.require('WAWebWidToJid').widToGroupJid(groupWid);
        
        const _getSleepTime = (sleep) => {
            if (!Array.isArray(sleep) || (sleep.length === 2 && sleep[0] === sleep[1])) {
                return sleep;
            }
            if (sleep.length === 1) {
                return sleep[0];
            }
            sleep[1] - sleep[0] < 100 && (sleep[0] = sleep[1]) && (sleep[1] += 100);
            return Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + sleep[0];
        };

        const membReqResCodes = {
            default: `An unknown error occupied while ${toApprove ? 'approving' : 'rejecting'} the participant membership request`,
            400: 'ParticipantNotFoundError',
            401: 'ParticipantNotAuthorizedError',
            403: 'ParticipantForbiddenError',
            404: 'ParticipantRequestNotFoundError',
            408: 'ParticipantTemporarilyBlockedError',
            409: 'ParticipantConflictError',
            412: 'ParticipantParentLinkedGroupsResourceConstraintError',
            500: 'ParticipantResourceConstraintError'
        };

        try {
            for (const participant of participantArgs) {
                response = await (window.require('WASmaxGroupsMembershipRequestsActionRPC')).sendMembershipRequestsActionRPC({
                    iqTo: groupJid,
                    [toApprove ? 'approveArgs' : 'rejectArgs']: participant
                });

                if (response.name === 'MembershipRequestsActionResponseSuccess') {
                    const value = toApprove
                        ? response.value.membershipRequestsActionApprove
                        : response.value.membershipRequestsActionReject;
                    if (value?.participant) {
                        const [_] = value.participant.map(p => {
                            const error = toApprove
                                ? value.participant[0].membershipRequestsActionAcceptParticipantMixins?.value.error
                                : value.participant[0].membershipRequestsActionRejectParticipantMixins?.value.error;
                            return {
                                requesterId: window.require('WAWebWidFactory').createWid(p.jid)._serialized,
                                ...(error
                                    ? { error: +error, message: membReqResCodes[error] || membReqResCodes.default }
                                    : { message: `${toApprove ? 'Approved' : 'Rejected'} successfully` })
                            };
                        });
                        _ && result.push(_);
                    }
                } else {
                    result.push({
                        requesterId: window.require('WAWebJidToWid').userJidToUserWid(participant.participantArgs[0].participantJid)._serialized,
                        message: 'ServerStatusCodeError'
                    });
                }

                sleep &&
                    participantArgs.length > 1 &&
                    participantArgs.indexOf(participant) !== participantArgs.length - 1 &&
                    (await new Promise((resolve) => setTimeout(resolve, _getSleepTime(sleep))));
            }
            return result;
        } catch (err) {
            return [];
        }
    };

    window.WWebJS.subscribeToUnsubscribeFromChannel = async (channelId, action, options = {}) => {
        const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });

        if (!channel || channel.newsletterMetadata.membershipType === 'owner') return false;
        options = { eventSurface: 3, deleteLocalModels: options.deleteLocalModels ?? true };

        try {
            if (action === 'Subscribe') {
                await (window.require('WAWebNewsletterSubscribeAction')).subscribeToNewsletterAction(channel, options);
            } else if (action === 'Unsubscribe') {
                await (window.require('WAWebNewsletterUnsubscribeAction')).unsubscribeFromNewsletterAction(channel, options);
            } else return false;
            return true;
        } catch (err) {
            if (err.name === 'ServerStatusCodeError') return false;
            throw err;
        }
    };

    window.WWebJS.pinUnpinMsgAction = async (msgId, action, duration) => {
        const message = (window.require('WAWebCollections')).Msg.get(msgId) || (await (window.require('WAWebCollections')).Msg.getMessagesById([msgId]))?.messages?.[0];
        if (!message) return false;

        if (typeof duration !== 'number') return false;
        
        const originalFunction = window.require('WAWebPinMsgConstants').getPinExpiryDuration;
        window.require('WAWebPinMsgConstants').getPinExpiryDuration = () => duration;
        
        const response = await (window.require('WAWebSendPinMessageAction')).sendPinInChatMsg(message, action, duration);

        window.require('WAWebPinMsgConstants').getPinExpiryDuration = originalFunction;

        return response.messageSendResult === 'OK';
    };
    
    window.WWebJS.getStatusModel = status => {
        const res = status.serialize();
        delete res._msgs;
        return res;
    };

    window.WWebJS.getAllStatuses = () => {
        const statuses = (window.require('WAWebCollections')).Status.getModelsArray();
        return statuses.map(status => window.WWebJS.getStatusModel(status));
    };

    window.WWebJS.enforceLidAndPnRetrieval = async (userId) => {
        const wid = window.require('WAWebWidFactory').createWid(userId);
        const isLid = wid.server === 'lid';

        let lid = isLid ? wid : window.require('WAWebApiContact').getCurrentLid(wid);
        let phone = isLid ? window.require('WAWebApiContact').getPhoneNumber(wid) : wid;

        if (!isLid && !lid) {
            const queryResult = await (window.require('WAWebQueryExistsJob').queryWidExists)(wid);
            if (!queryResult?.wid) return {};
            lid = window.require('WAWebApiContact').getCurrentLid(wid);
        }

        if (isLid && !phone) {
            const queryResult = await (window.require('WAWebQueryExistsJob').queryWidExists)(wid);
            if (!queryResult?.wid) return {};
            phone = window.require('WAWebApiContact').getPhoneNumber(wid);
        }

        return { lid, phone };
    };

    window.WWebJS.assertColor = (hex) => {
        let color;
        if (typeof hex === 'number') {
            color = hex > 0 ? hex : 0xffffffff + parseInt(hex) + 1;
        } else if (typeof hex === 'string') {
            let number = hex.trim().replace('#', '');
            if (number.length <= 6) {
                number = 'FF' + number.padStart(6, '0');
            }
            color = parseInt(number, 16);
        } else {
            throw 'Invalid hex color';
        }
        return color;
    };
};