/**
 * @author 1902 Software
 * @copyright Copyright © 2023 1902 Software (https://1902software.com/magento/)
 * @package WriteTextAI_WriteTextAI
 */

define([
    'jquery',
    'ko',
    'uiComponent',
    'WriteTextAI_WriteTextAI/js/model/edit/textfields',
    'WriteTextAI_WriteTextAI/js/model/edit/product',
    'WriteTextAI_WriteTextAI/js/model/grid/notifications',
    'WriteTextAI_WriteTextAI/js/model/signalr',
    'WriteTextAI_WriteTextAI/js/model/edit',
    'WriteTextAI_WriteTextAI/js/model/edit/gallery',
    'WriteTextAI_WriteTextAI/js/model/edit/feedback',
    'WriteTextAI_WriteTextAI/js/model/edit/mark-reviewed',
    'WriteTextAI_WriteTextAI/js/model/grid/listing',
    'WriteTextAI_WriteTextAI/js/model/edit/keywords/keyword-analysis',
    'WriteTextAI_WriteTextAI/js/model/edit/keywords/keywords',
    'WriteTextAI_WriteTextAI/js/model/grid/popups/keyword-analysis-progress',
    'WriteTextAI_WriteTextAI/js/model/edit/generate/currently-generating',
    'WriteTextAI_WriteTextAI/js/grid/reload',
    'WriteTextAI_WriteTextAI/js/utils/signalr',
    'WriteTextAI_WriteTextAI/js/model/edit/audience',
    'WriteTextAI_WriteTextAI/js/model/edit/keywords/progress',
    'wtaiSignalR',
    'mage/translate'
], function (
    $,
    ko,
    Component,
    textfields,
    product,
    notifications,
    signalRModel,
    editData,
    gallery,
    feedbackData,
    markReviewed,
    listingModel,
    keywordAnalysis,
    keywords,
    keywordAnalysisProgressModel,
    currentlyGeneratingModel,
    reloadGrid,
    signalrUtils,
    audience,
    keywordsProgressModel,
    signalR,
    $t
) {
    'use strict';

    /**
     * @var {Component} self
     */
    var self;

    return Component.extend({
        defaults: {
            productId: '',
            storeId: '',
            connection: null,
            retryReconnectAttempt: 0,
            enableMaxRetryAttempt: false,
            maxRetryAttempt: 10,
            accessToken: '',
            signalRUrl: '',
            singleGenerate: 0,
            bulkGenerate: 1,
            fieldStatus: 2,
            optimization: 4,
            snapshot: 5,
            openAnalysis: 6,
            transfer: 8,
            message: $t('Text generation completed'),
            showMessage: false,
            messageSuccess: false,
            successMessage: $t('Text generation completed'),
            pageTitle: '',
            pageDescription: '',
            productDescription: '',
            excerpt: '',
            openGraph: '',
            errorMessage: '',
            statusFailed: 'Failed',
            statusCompleted: 'Completed',
            userName: '',
            getBulkRequestsUrl: '',
            
            /** SignalR Connection Status Constants */
            connectionStatusDisconnected: 'Disconnected',
            connectionStatusConnecting: 'Connecting',
            connectionStatusConnected: 'Connected',
            connectionStatusReconnecting: 'Reconnecting',
            connectionStatusFailed: 'Failed'
        },        
        isFetchingGeneratedData: false,
        
        /** Web Lock API variables */
        lockResolver: null,
        wakeLockPromise: null,

        /** Web Lock API variables */
        lockResolver: null,
        wakeLockPromise: null,

        isGettingNewToken: false,
        gettingData: [],
        singleRequestChecker: [],
        latestMessageEntry: [],

        /** @inheritdoc */
        initialize: function () {
            this._super();

            self = this;
            
            /** Initialize connection status in model */
            this.updateConnectionStatus(this.connectionStatusDisconnected, '', false, 'Not initialized');

            this.connectTostream();

            product.productId.subscribe(function (productId) {
                self.productId(productId);
            });

            product.storeId.subscribe(function (storeId) {
                self.storeId(storeId);
            });

            signalRModel.showMessage.subscribe(function (show) {
                self.showMessage(show);
            });

            signalRModel.message.subscribe(function (message) {
                self.message(message);
            });

            listingModel.currentUser.subscribe(function (user) {
                self.userName(user.email);
            });

            signalRModel.isConnected.subscribe(function (isConnected) {
                if (isConnected) {
                    signalRModel.triggerContinueBulkTransfer(false);
                    self.fetchBulkRequests();
                    self.handleRefreshingData();
                }
            });

            signalRModel.triggerHandleUserReturn.subscribe(function (triggerHandleUserReturn) {
                if (triggerHandleUserReturn) {
                    self.handleUserReturn();
                    signalRModel.triggerHandleUserReturn(false);
                }
            });
        },

        /** @inheritdoc */
        initObservable: function () {
            this._super().observe([
                'productId',
                'storeId',
                'accessToken',
                'signalRUrl',
                'getBulkRequestsUrl',
                'message',
                'showMessage',
                'messageSuccess',
                'errorMessage',
                'latestMessageEntry',
                'userName'
            ]);

            this.generateComplete = ko.computed(function () {
                var fieldsGenerated = signalRModel.fieldsGenerated(),
                    fieldsValues = Object.values(fieldsGenerated),
                    hasTrue = fieldsValues.includes(true),
                    hasFalse = fieldsValues.includes(false),
                    imagesGenerating = signalRModel.imagesGenerating(),
                    imagesGenerated = signalRModel.imagesGenerated(),
                    imagesGeneratedLength = imagesGenerated ? imagesGenerated.length : 0,
                    imagesGeneratingLength = imagesGenerating ? imagesGenerating.length : 0;

                var isGenerating = signalRModel.generating(),
                    isTextsGenerating = hasTrue || hasFalse,
                    isTextsGenerated = hasTrue && !hasFalse,
                    isImagesGenerating = imagesGeneratingLength > 0 && imagesGeneratedLength > 0,
                    isImagesGenerated = imagesGeneratingLength === imagesGeneratedLength;

                if (isGenerating && (
                    (isTextsGenerating && !isImagesGenerating && isTextsGenerated) || /** texts only */
                    (!isTextsGenerating && isImagesGenerating && isImagesGenerated) || /** images only */
                    (isTextsGenerating && isImagesGenerating && isTextsGenerated && isImagesGenerated) /** texts and images */
                )) {
                    return true;
                }

                return false;
            });

            this.generateComplete.subscribe(function (generateComplete) {
                if (generateComplete) {
                    /**self.catchFieldsError();
                     *self.getGenerated();
                     */
                }
            });

            return this;
        },

        /**
         * Catch fields error.
         */
        catchFieldsError: function () {
            var fieldsGenerated = signalRModel.fieldsGenerated(),
                fieldsValues = Object.values(fieldsGenerated),
                hasTrue = fieldsValues.includes(true),
                hasFalse = fieldsValues.includes(false),
                imagesGenerating = signalRModel.imagesGenerating(),
                imagesGenerated = signalRModel.imagesGenerated(),
                imagesGeneratedLength = imagesGenerated ? imagesGenerated.length : 0,
                imagesGeneratingLength = imagesGenerating ? imagesGenerating.length : 0;

            var isTextsGenerating = hasTrue || hasFalse,
                isImagesGenerating = imagesGeneratingLength > 0 && imagesGeneratedLength > 0;

            self.errorMessage('');
            self.messageSuccess(true);
                
            switch (true) {
                case !isTextsGenerating && isImagesGenerating:
                    self.getAltTextsError();
                    break;
                case isTextsGenerating && !isImagesGenerating:
                    self.getNormalTextsError();
                    break;
                case isTextsGenerating && isImagesGenerating:
                    self.getAllTextsError();
                    break;
                default:
                    break;
            }
        },

        /**
         * Get all texts error.
         */
        getAllTextsError: function () {
            var errorFields = signalRModel.errorFields(),
                imagesHtml = '';

            if (gallery.failedImagesWithoutThumbnail().length > 0) {
                errorFields.push('alt text');
                /** gallery.failedImagesWithoutThumbnail().forEach(element => {
                    imagesHtml += '<img src="' + element + '">';
                }); */
            }
            
            var htmlMessage = $t('Error encountered while generating the following text: %s')
            .replace('%s', '<ul>' + errorFields.map(field => `<li>${field}</li>`).join('') + '</ul>');

            htmlMessage += imagesHtml;

            htmlMessage += self.getImageFailedMessage();
            
            if (gallery.failedImagesWithoutThumbnail().length > 0 || errorFields.length > 0) {
                signalRModel.message(self.successMessage);
                self.errorMessage(htmlMessage);
            }
        },

        /**
         * Get alt texts error.
         */
        getAltTextsError: function () {
            var errorFields = ['alt text'],
                imagesHtml = $t('Error encountered while generating the following text: %s')
                    .replace('%s', '<ul>' + errorFields.map(field => `<li>${field}</li>`).join('') + '</ul>');
            
            imagesHtml += self.getImageFailedMessage();
            
            if (gallery.failedImagesWithoutThumbnail().length > 0) {
                self.errorMessage(imagesHtml);
            }
        },

        /**
         * Get normal texts error.
         */
        getNormalTextsError: function () {
            var errorFields = signalRModel.errorFields(),
                selectedFields = signalRModel.selectedFields(),
                htmlMessage = $t('Error encountered while generating the following text: %s')
                    .replace('%s', '<ul>' + errorFields.map(field => `<li>${field}</li>`).join('') + '</ul>');

            htmlMessage += self.getImageFailedMessage();
                    
            if (selectedFields.length > 0 && selectedFields.length === errorFields.length) {
                self.messageSuccess(false);
                if (signalRModel.errorMessage()) {
                    signalRModel.message(signalRModel.errorMessage());
                } else {
                    signalRModel.message(self.successMessage);
                    self.errorMessage(htmlMessage);
                }
            } else if (errorFields.length > 0) {
                signalRModel.message(self.successMessage);
                self.errorMessage(htmlMessage);
            } else {
                self.errorMessage(self.getImageFailedMessage());
            }
        },

        /**
         * Get image failed message.
         *
         * @returns {String}
         */
        getImageFailedMessage: function () {
            var subMessage = $t('Image analysis and alt text will be skipped for products with invalid images, and image descriptions may be missing.'),
                imagesHtml = $t('Error encountered while uploading some product images. %s %s')
                    .replace('%s', '<ul></ul>')
                    .replace('%s', '<span class="wtai-submessage">' + subMessage + '</span>');
            if (gallery.failedImagesWithoutThumbnail().length > 0) {
                gallery.failedImagesWithoutThumbnail().forEach(element => {
                    imagesHtml += '<img src="' + element + '">';
                });

                return imagesHtml;
            }
            return '';
        },

        /**
         * Hide text generation success message.
         */
        hideMessage: function () {
            signalRModel.showMessage(false);
        },

        /**
         * Handle returning from idle state to sync generated texts.
         */
        handleUserReturn: function () {
            if (signalRModel.toGetGenerated() && !self.isFetchingGeneratedData) {
                var productId = self.productId(),
                    storeId = Number(self.storeId());
                self.catchFieldsError();
                self.getGenerated();
                if (gallery.selectedImages().length > 0) {
                    signalrUtils.getImages(productId, storeId, self);
                }
            }
        },

        /**
         * Handle refreshing data.
         */
        handleRefreshingData: function () {
            if (keywordAnalysis.refreshingData()) {
                self.getOptimization();
            }
        },

        /**
         * Initialize Web Lock to keep tab alive.
         */
        initializeWebLock: function () {
            if (navigator && navigator.locks && navigator.locks.request) {
                const promise = new Promise((res) => {
                    self.lockResolver = res;
                });

                self.wakeLockPromise = navigator.locks.request(
                    'signalr_connection_lock',
                    { mode: "shared" },
                    () => { return promise; }
                );
            }
        },

        /**
         * Release Web Lock.
         */
        releaseWebLock: function () {
            if (self.lockResolver) {
                self.lockResolver();
                self.lockResolver = null;
                self.wakeLockPromise = null;
            }
        },

        /**
         * Connect to signalR stream.
         */
        connectTostream: function () {
            if (self.accessToken()) {
                /** Initialize Web Lock to prevent tab from sleeping */
                self.initializeWebLock();

                self.updateConnectionStatus(self.connectionStatusConnecting, '', false, null);

                self.connection = new signalR.HubConnectionBuilder()
                    .withUrl(self.signalRUrl(), {
                        accessTokenFactory: () => self.accessToken()
                    })
                    .build();
                self.connection
                    .start()
                    .then(() => self.onConnected(self.connection))
                    .catch((error) => self.onConnectionError(error));

                self.bindConnectionMessageGenerate(self.connection);

                $(window).on('focus', function () {
                    if (
                        self.connection != null &&
                        self.connection.connectionState != "Connected" &&
                        self.connection.connectionState != "Connecting"
                    ) {
                        self.updateConnectionStatus(self.connectionStatusReconnecting, self.connection.connectionState, false, null);
                        self.connection.start();
                    }
                });

                /** Handle page unload to clean up web lock */
                $(window).on('beforeunload', function () {
                    self.releaseWebLock();
                });
            } else {
                self.updateConnectionStatus(self.connectionStatusDisconnected, '', false, 'No access token available');
            }
        },

        /**
         * Connection is established actions.
         *
         * @param {object} connection
         * @returns {void}
         */
        onConnected: function (connection) {
            self.updateConnectionStatus(self.connectionStatusConnected, connection.connectionState, true, null);
            /** Reset retry attempts on successful connection */
            self.retryReconnectAttempt = 0;
            signalRModel.retryAttempts(0);
            connection.send('broadcastMessage', '_SYSTEM_', 'Connected');
        },

        /**
         * Connection error actions.
         *
         * @param {object} error
         * @returns {void}
         */
        onConnectionError: function (error) {
            var doConnect = true;
            var errorMessage = error && error.message ? error.message : 'Connection error';

            self.updateConnectionStatus(self.connectionStatusDisconnected, '', false, errorMessage);

            if (error.statusCode === 401 && !self.isGettingNewToken) {
                signalrUtils.getNewAccessToken(self);
            }

            if (self.enableMaxRetryAttempt && self.retryReconnectAttempt >= self.maxRetryAttempt) {
                doConnect = false;
                self.updateConnectionStatus(self.connectionStatusFailed, '', false, 'Max retry attempts reached');
            }

            if (doConnect) {
                signalRModel.retryAttempts(self.retryReconnectAttempt);
                
                if (self.retryReconnectAttempt === 0) {
                    self.connectTostream();
                } else {
                    self.updateConnectionStatus(self.connectionStatusReconnecting, '', false, 'Retrying connection in 5 seconds...');
                    setTimeout(function () {
                        self.connectTostream(); /* attempt to connect every 5 seconds */
                    }, 5000);
                }

                self.retryReconnectAttempt++;
            }
        },

        /**
         * Update connection status in model.
         *
         * @param {String} status
         * @param {String} state
         * @param {Boolean} connected
         * @param {String} error
         * @returns {void}
         */
        updateConnectionStatus: function (status, state, connected, error) {
            signalRModel.connectionStatus(status);
            signalRModel.connectionState(state || '');
            signalRModel.isConnected(connected);
            signalRModel.connectionError(error || '');
            
            if (connected) {
                signalRModel.lastConnectionTime(new Date().toISOString());
            }
            
            console.log('SignalR Connection Status Updated:', {
                status: status,
                state: state,
                connected: connected,
                error: error,
                retryAttempts: signalRModel.retryAttempts()
            });
        },

        /**
         * Bind connection messages.
         *
         * @param {Object} connection
         * @returns {void}
         */
        bindConnectionMessageGenerate: function (connection) {
            if (connection === null) {
                /* bypass */
            }

            connection.on('broadcastMessage', self.messageCallbackWrapper);
            connection.onclose(self.onConnectionError);
            if (connection.onreconnecting) {
                connection.onreconnecting((error) => {
                    self.updateConnectionStatus(self.connectionStatusReconnecting, connection.connectionState, false, error ? error.message : null);
                });
            }
            
            if (connection.onreconnected) {
                connection.onreconnected((connectionId) => {
                    self.updateConnectionStatus(self.connectionStatusConnected, connection.connectionState, true, null);
                    console.log('SignalR reconnected with connection ID:', connectionId);
                });
            }
        },

        /**
         * Parse message.
         *
         * @param {String} encodedName
         * @param {String} encodedMsg
         *
         * @returns {Object}
         */
        createMessageEntry: function (encodedName, encodedMsg) {
            return {
                encodedName: encodedName,
                encodedMsg: JSON.parse(encodedMsg)
            };
        },

        /**
         * Message callback wrapper
         */
        messageCallbackWrapper: function (name, message) {
            try {
                self.messageCallback(name, message);
            } catch (error) {
                console.log('Error in messageCallbackWrapper:', error);
            }
        },

        /**
         * Process stream message.
         *
         * @param {String} name
         * @param {String} message
         *
         * @returns {void}
         */
        messageCallback: function (name, message) {
            if (message === 'Connected') {
                return;
            }
            var encodedName = name,
                encodedMsg = message
                    .replace(/&/g, '&amp;')
                    .replace(/</g, '&lt;')
                    .replace(/>/g, '&gt;'),
                messageEntry = self.createMessageEntry(encodedName, encodedMsg),
                streamRecordType = messageEntry.encodedMsg.recordType,
                streamType = messageEntry.encodedMsg.type,
                streamSubType = messageEntry.encodedMsg.subType,
                streamFieldType = messageEntry.encodedMsg.field,
                streamStop = messageEntry.encodedMsg.stop,
                streamPartialText = messageEntry.encodedMsg.partialText,
                streamRecordId = messageEntry.encodedMsg.recordId,
                streamStoreId = Number(messageEntry.encodedMsg.storeId),
                streamIndex = messageEntry.encodedMsg.index,
                streamQueuedIds = messageEntry.encodedMsg.queuedIds,
                streamRunningIds = messageEntry.encodedMsg.runningIds,
                streamFailedIds = messageEntry.encodedMsg.failedIds,
                streamFailedTransferIds = messageEntry.encodedMsg.failedTransferIds,
                streamCompletedIds = messageEntry.encodedMsg.completedIds,
                streamUserName = messageEntry.encodedMsg.userName,
                generationType = messageEntry.encodedMsg.generationType,
                indexField = '',
                streamStatus = messageEntry.encodedMsg.status,
                fieldsGenerated = {},
                productId = self.productId(),
                storeId = Number(self.storeId()),
                entityType = self.entityType,
                streamRecordType = messageEntry.encodedMsg.recordType;

            console.log(messageEntry.encodedMsg);
            switch (streamType) {
                case self.fieldStatus:
                    if (signalRModel.generating() === false) {
                        break;
                    }
                        
                    if (streamStatus === self.statusFailed) {
                        signalRModel.errorFields.push(streamFieldType);
                        signalRModel.errorMessage(messageEntry.encodedMsg.error);
                    }
                    
                    if (productId === streamRecordId && storeId === streamStoreId) {
                        indexField = signalrUtils.getIndexField(streamFieldType, self);
        
                        if (streamFieldType !== "alt text") {
                            fieldsGenerated = signalRModel.fieldsGenerated();
                            fieldsGenerated[indexField] = true;
                            signalRModel.fieldsGenerated(fieldsGenerated);
                            if (productId === streamRecordId && storeId === streamStoreId) {
                                var statuses = textfields.statuses();
                                switch (indexField) {
                                    case 'page_title':
                                        $('.wtai-page-title-loading-mask').removeClass('wtai-show');
                                        statuses['pageTitleGenerateStatus'] = true;
                                        break;
                                    case 'page_description':
                                        $('.wtai-page-description-loading-mask').removeClass('wtai-show');
                                        statuses['pageDescriptionGenerateStatus'] = true;
                                        break;
                                    case 'product_description':
                                        $('.wtai-product-description-loading-mask').removeClass('wtai-show');
                                        statuses['productDescriptionGenerateStatus'] = true;
                                        break;
                                    case 'excerpt':
                                        $('.wtai-short-description-loading-mask').removeClass('wtai-show');
                                        statuses['productShortDescriptionGenerateStatus'] = true;
                                        break;
                                    case 'open_graph':
                                        $('.wtai-open-graph-loading-mask').removeClass('wtai-show');
                                        statuses['openGraphGenerateStatus'] = true;
                                        break;
                                }
                                
                                textfields.statuses(statuses);
                            }
                        }
                    }
                    if (streamFieldType === "alt text" && streamStatus === self.statusCompleted) {
                        signalRModel.imagesGenerated([...signalRModel.imagesGenerated(), ...streamRecordId.split(",")]);
                        if (productId) {
                            /** signalrUtils.getImages(productId, storeId, self); */
                        }
                    }
                    break;
                case self.singleGenerate:
                    /** remove identifier from currently generating record identifiers if status is failed */
                    var identifier = entityType + '_' +  streamRecordId + '_' + streamStoreId;
                    var currentlyGeneratingRecordIdentifiers = currentlyGeneratingModel.recordIdentifiers();
                    if (currentlyGeneratingRecordIdentifiers.includes(identifier) && streamStatus === self.statusFailed) {
                        currentlyGeneratingRecordIdentifiers.splice(currentlyGeneratingRecordIdentifiers.indexOf(identifier), 1);
                        currentlyGeneratingModel.recordIdentifiers(currentlyGeneratingRecordIdentifiers);
                    }

                    var existingIndex = signalRModel.singleGeneratingStatuses().findIndex(function (status) {
                        return status.recordId === streamRecordId &&
                               Number(status.storeId) === streamStoreId &&
                               status.generationType === generationType &&
                               status.field === streamFieldType;
                    });
                    if (existingIndex > -1) {
                        signalRModel.singleGeneratingStatuses.splice(existingIndex, 1, messageEntry.encodedMsg);
                    } else {
                        signalRModel.singleGeneratingStatuses.push(messageEntry.encodedMsg);
                    }

                    if (signalRModel.generating() === true) {
                        
                        /** add identifier to currently generating record identifiers if status is not failed */
                        var currentRecordIdentifiers = currentlyGeneratingModel.recordIdentifiers();
                        var identifier = self.entityType + '_' + streamRecordId + '_' + streamStoreId;
                        if (!currentRecordIdentifiers.includes(identifier) && streamStatus !== self.statusFailed) {
                            currentRecordIdentifiers.push(identifier);
                            currentlyGeneratingModel.recordIdentifiers(currentRecordIdentifiers);
                        }
                    }

                    if (productId === streamRecordId && storeId === streamStoreId) {
                        indexField = signalrUtils.getIndexField(streamFieldType, self);

                        /* eslint-disable max-depth */
                        if (streamIndex === signalRModel.index()[indexField]) {
                            self.processSingleGenerate(
                                streamFieldType,
                                streamStop,
                                streamPartialText,
                                streamIndex
                            );
                            streamIndex += 1;
                            signalRModel.index()[indexField] = streamIndex;
    
                            while (
                                typeof signalRModel.messages()[indexField][
                                    streamIndex
                                ] !== 'undefined'
                            ) {
                                self.processSingleGenerate(
                                    signalRModel.messages()[indexField][streamIndex]
                                        .encodedMsg.field,
                                    signalRModel.messages()[indexField][streamIndex]
                                        .encodedMsg.stop,
                                    signalRModel.messages()[indexField][streamIndex]
                                        .encodedMsg.partialText,
                                    signalRModel.messages()[indexField][streamIndex]
                                        .encodedMsg.index
                                );
                                streamIndex += 1;
                                signalRModel.index()[indexField] = streamIndex;
                            }
                        } else {
                            signalRModel.messages()[indexField][streamIndex] =
                                messageEntry;
                        }
    
                        if (streamStop === true) {
                            /* generate end */
                            signalRModel.index()[indexField] = 0;
                            signalRModel.messages()[indexField] = {};
                            if (signalRModel.generating() === false) {
                                self.repopulateTextFields();
                            }
                        }
                        /* eslint-enable max-depth */
                    }
                    break;
                case self.bulkGenerate:
                    if (streamRecordType === 'Category') {
                        break;
                    }

                    var requestFailedIds = [],
                        requestFailed = 0,
                        requestCompletedIds = [],
                        requestCompleted = 0;

                    if (['Completed', 'Failed', 'TimedOut'].includes(streamStatus)) {

                        /** Merge all failure-related arrays */
                        const allFailed = [
                            ...streamFailedIds,
                            ...streamRunningIds,
                            ...streamQueuedIds,
                            ...streamFailedTransferIds
                        ];

                        /** Deduplicate failed IDs */
                        requestFailedIds = [...new Set(allFailed)];
                        requestFailed = requestFailedIds.length;

                        /** Remove all failed IDs from completed IDs */
                        const failedSet = new Set(requestFailedIds);
                        requestCompletedIds = streamCompletedIds.filter(id => !failedSet.has(id));
                        requestCompleted = requestCompletedIds.length;
                    }

                    switch (streamSubType) {
                        case 0:
                        case 1:
                            /** remove identifier from currently generating record identifiers if status is failed */
                            var identifier = entityType + '_' +  streamRecordId + '_' + streamStoreId;
                            var currentlyGeneratingRecordIdentifiers = currentlyGeneratingModel.recordIdentifiers();
                            if (currentlyGeneratingRecordIdentifiers.includes(identifier) && streamStatus === self.statusFailed) {
                                currentlyGeneratingRecordIdentifiers.splice(currentlyGeneratingRecordIdentifiers.indexOf(identifier), 1);
                                currentlyGeneratingModel.recordIdentifiers(currentlyGeneratingRecordIdentifiers);
                            }

                            /** Suppress bulk generate progress bar if keyword analysis is ongoing */
                            if (messageEntry.encodedMsg.id.includes('KeywordOptimization') || (
                                messageEntry.encodedMsg.total === 1
                            )) {
                                var queuedIds = streamQueuedIds;
                                queuedIds.forEach((id) => {
                                    var identifier = entityType + '_' +  id + '_' + streamStoreId;
                                    var currentlyGeneratingRecordIdentifiers = currentlyGeneratingModel.recordIdentifiers();
                                    if (!currentlyGeneratingRecordIdentifiers.includes(identifier) && streamStatus !== self.statusFailed) {
                                        currentlyGeneratingRecordIdentifiers.push(identifier);
                                        currentlyGeneratingModel.recordIdentifiers(currentlyGeneratingRecordIdentifiers);
                                    }
                                });
                                var isBulkSingle = notifications.currentBulkRequestId() === messageEntry.encodedMsg.id;
                                var isFromBulk = notifications.currentBulkRequestId() === notifications.tempId;
                                if ((notifications.currentBulkRequestId() && isBulkSingle) || isFromBulk) {
                                    // signalrUtils.processSingleRequest(
                                    //     messageEntry,
                                    //     self,
                                    //     false
                                    // );
                                    
                                    notifications.addStreamStatus(messageEntry);
                                }

                                if (!isBulkSingle && editData.opened()) {
                                    signalrUtils.processSingleRequest(
                                        messageEntry,
                                        self,
                                        true
                                    );
                                }

                                if (streamSubType === 1) {
                                    /** Use the model's method to add or update status */
                                    keywordsProgressModel.addOrUpdateStatus(messageEntry.encodedMsg);
                                }

                                if (['Completed', 'Failed', 'TimedOut', 'Cancelled'].includes(streamStatus)) {
                                    if (signalRModel.generating() === true && 
                                        storeId === streamStoreId &&
                                        (streamCompletedIds.includes(productId) ||
                                        streamFailedIds.includes(productId) ||
                                        streamRunningIds.includes(productId)) &&
                                        requestCompleted !== 0
                                    ) {
                                        self.catchFieldsError();
                                        self.getGenerated();
                                        if (signalRModel.imagesGenerated().length > 0) {
                                            signalrUtils.getImages(productId, storeId, self);
                                        }
                                    }

                                    var completedId = messageEntry.encodedMsg.completedIds;
                                    completedId.forEach((id) => {
                                        var identifier = entityType + '_' +  id + '_' + streamStoreId;
                                        var currentlyGeneratingRecordIdentifiers = currentlyGeneratingModel.recordIdentifiers();
                                        if (currentlyGeneratingRecordIdentifiers.includes(identifier)) {
                                            currentlyGeneratingRecordIdentifiers.splice(currentlyGeneratingRecordIdentifiers.indexOf(identifier), 1);
                                            currentlyGeneratingModel.recordIdentifiers(currentlyGeneratingRecordIdentifiers);
                                        }
                                    });
                                }
                            } else {
                                /** Suppress bulk generate progress bar if keyword analysis is ongoing */
                                /** if (messageEntry.encodedMsg.status === 'Failed') {
                                 * notifications.addGenerateFailedStatus(messageEntry.encodedMsg);
                                 * } else {
                                 * notifications.addStreamStatus(messageEntry);
                                 * } */
                                
                                notifications.addStreamStatus(messageEntry);
                            }

                            if (['Cancelled', 'Completed', 'Failed'].includes(streamStatus)) {
                                signalrUtils.getCredits(self);
                            }
                            break;
                        case 2:
                            var isBulkSingle = messageEntry.encodedMsg.total === 1 &&
                                notifications.currentBulkRequestId() === messageEntry.encodedMsg.id;
                            if ((!messageEntry.encodedMsg.id.includes('KeywordOptimization') && messageEntry.encodedMsg.total > 1)
                                || isBulkSingle) {
                                if (messageEntry.encodedMsg.total === 1
                                    && messageEntry.encodedMsg.status === 'Completed'
                                    && messageEntry.encodedMsg.completed === 0) {
                                    signalrUtils.getCredits(self);
                                    break;
                                }
                                if (streamStatus === 'Completed' &&
                                    messageEntry.encodedMsg.completed === 1 &&
                                    messageEntry.encodedMsg.failed === 0
                                ) {
                                    self.processFailedOptimizationMessage(messageEntry);
                                } else {
                                    notifications.addStreamStatus(messageEntry);
                                }
                                
                                keywordAnalysisProgressModel.status(messageEntry.encodedMsg);
                            } else {
                                /** Use the model's method to add or update status */
                                keywordsProgressModel.addOrUpdateStatus(messageEntry.encodedMsg);
                            }
                            if (streamStatus === 'Completed'
                                && messageEntry.encodedMsg.completed >= 1
                                && streamRecordType === entityType) {
                                /** reload grid */
                                self.gridReload();
                            }
                            
                            if (['Cancelled', 'Completed', 'Failed'].includes(streamStatus)) {
                                signalrUtils.getCredits(self);
                            }
                            break;
                        case 3:
                            if (messageEntry.encodedMsg.total === 1 && streamUserName !== self.userName()) {
                                break;
                            }
                            notifications.addStreamStatus(messageEntry);
                            
                            if (['Completed', 'Cancelled', 'Failed'].includes(streamStatus)) {
                                //remove status
                                if (streamUserName !== self.userName()) {
                                    notifications.removeTransferStatus(streamUserName);
                                }
                                self.gridReload();
                            }
                            break;
                        default:
                            break;
                    }
                    if (
                        ['Completed', 'Cancelled', 'Failed'].includes(streamStatus)
                        && streamUserName !== self.userName()
                    ) {
                        notifications.statuses.remove(function (status) {
                            return status.id === messageEntry.encodedMsg.id;
                        });
                    }
                    
                    if (['Completed', 'Failed', 'Cancelled', 'TimedOut'].includes(streamStatus)
                        && streamRecordType === entityType) {
                        /** reload grid */
                        self.gridReload();
                    }
                    break;
                case self.optimization:
                    if (streamRecordType === 'Category') {
                        break;
                    }
                    var statuses = keywordAnalysis.statuses(),
                        existingIndex = -1;

                    statuses.forEach(function (status, index) {
                        if (Number(status.storeId) === streamStoreId && status.recordId === streamRecordId) {
                            existingIndex = index;
                        }
                    });

                    if (existingIndex >= 0) {
                        statuses[existingIndex] = messageEntry.encodedMsg;
                    } else {
                        statuses.push(messageEntry.encodedMsg);
                    }

                    keywordAnalysis.statuses(statuses);

                    if (messageEntry.encodedMsg.status === 'Failed'
                        && self.productId() === streamRecordId && storeId === streamStoreId
                    ) {
                        if (signalRModel.generating() === true) {
                            self.repopulateTextFields();
                        }
                        /** reset optimizing id if optimization failed
                         * This will allow the generation progress bar to continue even if optimization failed
                        */
                        keywordAnalysis.optimizingId("");
                    }
                    break;
                case self.snapshot:
                    if (productId === streamRecordId && storeId === streamStoreId) {
                        keywordAnalysis.refreshingData(true);
                        if (streamStatus === self.statusCompleted) {
                            keywordAnalysis.refreshingData(false);
                            signalrUtils.getCredits(self);
                            self.getOptimization();
                            self.gridReload();
                        }
                    }
                    break;
                case self.openAnalysis:
                    /** $('.wtai-keyword-analysis').modal('openModal'); */
                case self.transfer:
                    if (messageEntry.encodedMsg.total === 1 && streamUserName !== self.userName()) {
                        break;
                    }
                    if (messageEntry.encodedMsg.status === 'Cancelled') {
                        notifications.updateTransferStatus(
                            [...streamQueuedIds],
                            streamUserName,
                            streamQueuedIds,
                            streamCompletedIds,
                            streamStatus
                        );
                        break;
                    }
                    notifications.updateTransferStatus(
                        [...streamQueuedIds, ...streamCompletedIds],
                        streamUserName,
                        streamQueuedIds,
                        streamCompletedIds,
                        streamStatus
                    );
                    
                    if (['Completed', 'Cancelled', 'Failed'].includes(streamStatus)) {
                        //remove status
                        if (streamUserName !== self.userName()) {
                            notifications.removeTransferStatus(streamUserName);
                        }
                        self.gridReload();
                    }
                    break;
            }
        },

        /**
         * Process single generate.
         *
         * @param {String} streamFieldType
         * @param {Boolean} streamStop
         * @param {String} streamPartialText
         * @param {Number} streamIndex
         *
         * @returns {void}
         */
        processSingleGenerate: function (
            streamFieldType,
            streamStop,
            streamPartialText,
            streamIndex
        ) {
            var currentValue = signalrUtils.getField(streamFieldType, self),
                newValue = currentValue + streamPartialText;

            /* generate starts */
            if (streamIndex === 0) {
                signalrUtils.updateField(streamFieldType, streamPartialText, self);
            }

            /* generate partial */
            if (
                streamPartialText !== null &&
                streamIndex !== null &&
                streamIndex !== 0 &&
                streamStop === false
            ) {
                signalrUtils.updateField(streamFieldType, newValue, self);
            }
        },

        /**
         * Get generated text fields.
         *
         * @returns {void}
         */
        getGenerated: function () {
            var url = self.getGeneratedUrl,
                statuses = textfields.statuses(),
                productId = self.productId(),
                storeId = self.storeId(),
                selectedFields = signalRModel.selectedFields() || [],
                imagesGenerated = signalRModel.imagesGenerated(),
                entityType = self.entityType;

            self.isFetchingGeneratedData = true;
            $.ajax({
                url: url,
                type: 'POST',
                data: {
                    product_id: productId,
                    store_id: storeId
                },
                dataType: 'json',
                showWriteTextAILoader: true,
                showPageTitleLoader: selectedFields.includes('page_title'),
                showPageDescriptionLoader: selectedFields.includes('page_description'),
                showProductDescriptionLoader: selectedFields.includes('product_description'),
                showShortDescriptionLoader: selectedFields.includes('short_product_description'),
                showOpenGraphLoader: selectedFields.includes('open_graph'),
                showGalleryLoader: imagesGenerated.length > 0,
                success: function (response) {
                    if (response.success) {
                        self.updateTextFields(response, selectedFields);

                        if (editData.opened()) {
                            signalRModel.showMessage(true);
                            self.showMessage(true);
                        }
                        signalRModel.generating(false);
                        signalRModel.editTitleStatusText('');
                        signalRModel.editTitleStatusText.valueHasMutated();

                        /** remove identifier from currently generating record identifiers if done generating */
                        var currentRecordIdentifiers = currentlyGeneratingModel.recordIdentifiers();
                        var identifier = self.entityType + '_' +  productId + '_' + storeId;
                        if (currentRecordIdentifiers.includes(identifier)) {
                            currentRecordIdentifiers.splice(currentRecordIdentifiers.indexOf(identifier), 1);
                            currentlyGeneratingModel.recordIdentifiers(currentRecordIdentifiers);
                        }

                        if (typeof response.feedback !== 'undefined') {
                            feedbackData.feedbacks(response.feedback);
                        }

                        if (typeof response.reviewed !== 'undefined') {
                            markReviewed.reviewed(response.reviewed);
                        }
                    } else {
                        console.log(response.message);
                    }
                    
                    if (keywordAnalysis.autoGenerateOrRewriteChecked() &&
                        keywordAnalysis.optimizingId() === self.productId()
                    ) {
                        if ($('.wtai-edit-modal.wtai-edit-main').hasClass('_show')) {
                            keywords.showProgress(false);
                            self.getOptimization();
                        }
                        keywordAnalysis.autoGenerateOrRewriteChecked(false);
                    }
                }
            }).always(function () {
                self.isFetchingGeneratedData = false;
                signalRModel.toGetGenerated(false);
            });
        },
        
        /**
         * Repopulate text fields.
         *
         * @returns {void}
         */
        repopulateTextFields: function () {
            var defaultSelectedFields = signalRModel.selectedFields();
            var url = self.getGeneratedUrl,
                productId = self.productId(),
                storeId = self.storeId(),
                selectedFields = [
                    'page_title',
                    'page_description',
                    'product_description',
                    'short_product_description',
                    'open_graph'
                ],
                imagesGenerated = signalRModel.imagesGenerated();
                
            if (self.gettingData['repopulateTextFields'] !== undefined ||
                self.gettingData['repopulateTextFields']) {
                self.gettingData['repopulateTextFields'].abort();
            }

            self.gettingData['repopulateTextFields'] = $.ajax({
                url: url,
                type: 'POST',
                data: {
                    product_id: productId,
                    store_id: storeId
                },
                dataType: 'json',
                showWriteTextAILoader: true,
                showPageTitleLoader: selectedFields.includes('page_title'),
                showPageDescriptionLoader: selectedFields.includes('page_description'),
                showProductDescriptionLoader: selectedFields.includes('product_description'),
                showShortDescriptionLoader: selectedFields.includes('short_product_description'),
                showOpenGraphLoader: selectedFields.includes('open_graph'),
                showGalleryLoader: imagesGenerated.length > 0,
                success: function (response) {
                    if (response.success) {
                        self.updateTextFields(response,selectedFields);
                        signalRModel.generating(false);
                        
                        /** remove identifier from currently generating record identifiers if failed */
                        var currentRecordIdentifiers = currentlyGeneratingModel.recordIdentifiers();
                        var identifier = self.entityType + '_' + productId + '_' + storeId;
                        if (currentRecordIdentifiers.includes(identifier)) {
                            currentRecordIdentifiers.splice(currentRecordIdentifiers.indexOf(identifier), 1);
                            currentlyGeneratingModel.recordIdentifiers(currentRecordIdentifiers);
                        }

                        if (typeof response.feedback !== 'undefined') {
                            feedbackData.feedbacks(response.feedback);
                        }
                    } else {
                        console.log(response.message);
                    }
                },
                error: function (xhr, status, error) {
                    if (status !== 'abort') {
                        console.log(error);
                    }
                }
            });
        },

        /**
         * Update text fields.
         *
         * @param {Array} selectedFields
         *
         * @returns {void}
         */
        updateTextFields: function (response, selectedFields) {
            var statuses = textfields.statuses();
            selectedFields.forEach((field) => {
                switch (field) {
                    case 'page_title':
                        if (response.textfields.page_title !== undefined) {
                            textfields.pageTitle(
                                response.textfields.page_title
                            );
                            textfields.originalPageTitle(
                                response.textfields.page_title
                            );
                            statuses['pageTitleGenerateStatus'] = true;
                            statuses['pageTitleTransferStatus'] = false;
                            $('#wtaiPageTitleMask').removeClass('wtai-generated');
                            $('#wtaiPageTitleMask').addClass('wtai-generated');
                        }
                        break;
                    case 'page_description':
                        if (response.textfields.page_description !== undefined) {
                            textfields.pageDescription(
                                response.textfields.page_description
                            );
                            textfields.originalPageDescription(
                                response.textfields.page_description
                            );
                            statuses['pageDescriptionGenerateStatus'] = true;
                            statuses['pageDescriptionTransferStatus'] = false;
                            $('#wtaiPageDescMask').removeClass('wtai-generated');
                            $('#wtaiPageDescMask').addClass('wtai-generated');
                        }
                        break;
                    case 'product_description':
                        if (response.textfields.product_description !== undefined) {
                            textfields.productDescription(
                                response.textfields.product_description
                            );
                            textfields.originalProductDescription(
                                response.textfields.product_description
                            );
                            statuses['productDescriptionGenerateStatus'] = true;
                            statuses['productDescriptionTransferStatus'] = false;
                            $('#wtaiProductDescMask + .mce-tinymce').removeClass('wtai-generated');
                            $('#wtaiProductDescMask + .mce-tinymce').addClass('wtai-generated');
                            $('#wtaiProductDescMask + .tox-tinymce').removeClass('wtai-generated');
                            $('#wtaiProductDescMask + .tox-tinymce').addClass('wtai-generated');
                        }
                        break;
                    case 'short_product_description':
                        if (response.textfields.short_product_description !== undefined) {
                            textfields.productShortDescription(
                                response.textfields
                                    .short_product_description
                            );
                            textfields.originalProductShortDescription(
                                response.textfields
                                    .short_product_description
                            );
                            statuses['productShortDescriptionGenerateStatus'] = true;
                            statuses['productShortDescriptionTransferStatus'] = false;
                            $('#wtaiExcerptMask + .mce-tinymce').removeClass('wtai-generated');
                            $('#wtaiExcerptMask + .mce-tinymce').addClass('wtai-generated');
                            $('#wtaiExcerptMask + .tox-tinymce').removeClass('wtai-generated');
                            $('#wtaiExcerptMask + .tox-tinymce').addClass('wtai-generated');
                        }
                        break;
                    case 'open_graph':
                        if (response.textfields.open_graph !== undefined) {
                            textfields.openGraph(
                                response.textfields.open_graph
                            );
                            textfields.originalOpenGraph(
                                response.textfields.open_graph
                            );
                            statuses['openGraphGenerateStatus'] = true;
                            statuses['openGraphTransferStatus'] = false;
                            $('#wtaiOpenGraphMask').removeClass('wtai-generated');
                            $('#wtaiOpenGraphMask').addClass('wtai-generated');
                        }
                        break;
                }
                textfields.statuses(statuses);
            });
        },

        /**
         * Get optimization.
         *
         * @returns {void}
         */
        getOptimization: function (openModal = false) {
            $.ajax({
                url: self.getOptimizationUrl,
                type: 'POST',
                data: {
                    store_id: self.storeId(),
                    record_id: self.productId(),
                    entity_type: self.entityType
                },
                dataType: 'json',
                showLoader: false,
                success: function (response) {
                    keywordAnalysis.optimizing(false);
                    keywordAnalysis.optimized(true);
                    keywordAnalysis.optimizationData(response.api_response);
                    keywordAnalysis.snapshotData(response.api_response.snapshots || {});

                    if (keywordAnalysis.refreshingData()) {
                        var isRefreshOngoing = response.api_response.refreshStatus === 'InProgress';
                        keywordAnalysis.refreshingData(isRefreshOngoing);
                    }

                    if (openModal && keywordAnalysis.optimizationData() && keywordAnalysis.optimizationData().status === 'Completed') {
                        $('.wtai-keyword-analysis').modal('openModal');
                    }
                }
            }).always(function () {
                keywords.showProgress(false);
            });
        },

        /**
         * Get optimization list.
         *
         * @param {Object} messageEntry
         *
         * @returns {void}
         */
        processFailedOptimizationMessage: function (messageEntry) {
            var message = messageEntry.encodedMsg;
            var storeId = message.storeId;
            var status = ['Failed'];
            var recordIds = message.completedIds;
            var entityType = self.entityType;

            $.ajax({
                url: self.getOptimizationListUrl,
                type: 'POST',
                data: {
                    store_id: storeId,
                    status: status,
                    record_ids: recordIds,
                    entity_type: entityType
                },
                dataType: 'json',
                showLoader: false,
                success: function (response) {
                    if (response.success) {
                        var optimizationList = response.api_response;
                        var targetRecordIdsStr = recordIds.map(String);
                        var currentUser = listingModel.currentUser();
                        
                        if (optimizationList.result.length === 0) {
                            notifications.addStreamStatus(messageEntry);
                            return;
                        }

                        var exists = optimizationList.result.some(record => targetRecordIdsStr.includes(record.recordId));
                        if (exists) {
                            notifications.addKeywordAnalysisFailedStatus(currentUser, message);
                        } else {
                            notifications.addStreamStatus(messageEntry);
                        }
                    }
                }
            });
        },

        /**
         * Fetch bulk requests via AJAX.
         *
         * @returns {void}
         */
        fetchBulkRequests: function () {
            if (!self.getBulkRequestsUrl()) {
                console.warn('Bulk requests URL not configured');
                return;
            }

            $.ajax({
                url: self.getBulkRequestsUrl(),
                type: 'GET',
                dataType: 'json',
                showLoader: false,
                success: function (response) {
                    if (response.success) {
                        /*remove all non-complete statuses before processing*/
                        notifications.statuses([]);
                        self.processBulkRequests(response.bulk_requests);
                        /*self.processTransferRequests(response.bulk_requests.transfer_statuses);*/
                        self.processSingleGenerateDisabled();
                        /** reload grid */
                        self.gridReload();
                        signalRModel.triggerContinueBulkTransfer(true);
                    } else {
                        console.error('Error fetching bulk requests:', response.message);
                    }
                },
                error: function (xhr, status, error) {
                    if (status !== 'abort') {
                        console.error('AJAX error fetching bulk requests:', error);
                    }
                }
            });
        },

        /**
         * processSingleGenerateDisabled
         */
        processSingleGenerateDisabled: function () {
            var currentlyGeneratingRecordIdentifiers = currentlyGeneratingModel.recordIdentifiers();
            var currentStatuses = notifications.statuses();
            var statuses = currentStatuses.filter(function (status) {
                return status.status === self.statusRunning && status.recordType === 'Product';
            });
            //reset record identifiers
            var newCurrentlyGeneratingRecordIdentifiers = [];
            //add to currentlyGeneratingRecordIdentifiers if is in statuses. only if single
            statuses.forEach(function (status) {
                var recordIdentifier = status.recordType + '_' + status.recordId + '_' + status.storeId;
                if (!currentlyGeneratingRecordIdentifiers.includes(recordIdentifier) &&
                    (status.subType === 0 || status.subType === 1) && status.total === 1) {
                    newCurrentlyGeneratingRecordIdentifiers.push(recordIdentifier);
                }
            });
            currentlyGeneratingModel.recordIdentifiers(newCurrentlyGeneratingRecordIdentifiers);
        },

        /**
         * Process fetched bulk requests.
         *
         * @param {Array} bulkRequests
         * @returns {void}
         */
        processBulkRequests: function (bulkRequests) {
            /** Process the bulk requests data as needed
             * This method can be extended based on specific requirements
             */
            if (bulkRequests && bulkRequests.all_statuses && bulkRequests.all_statuses.length > 0) {
                /** var currentStatuses = notifications.statuses();
                * var allStatuses = bulkRequests.all_statuses;
                * for (var i = 0; i < currentStatuses.length; i++) {
                *     var currentStatus = currentStatuses[i];
                *     var statusId = currentStatus.id;
                *     var statusIndex = allStatuses.findIndex(function (status) {
                *         return status.id === statusId;
                *     });
                *     if (statusIndex === -1) {
                *         continue;
                *     }
                *     var status = allStatuses[statusIndex];
                *     if (status.status === 'Completed'
                *         || status.status === 'Failed'
                *         || status.status === 'TimedOut') {
                *         currentStatuses[i] = status;
                *         var koStatuses = keywordAnalysis.statuses();
                *         var statusRecordIds = [...new Set([...status.completedIds, ...status.failedIds, ...status.queuedIds])];
                *         koStatuses.forEach(function (koStatus, index) {
                *             if (Number(koStatus.storeId) === Number(status.storeId) &&
                *                     statusRecordIds.includes(Number(koStatus.recordId))) {
                *                 koStatuses.splice(index, 1);
                *             }
                *         });
                *         keywordAnalysis.statuses(koStatuses);
                *         keywordAnalysis.statuses.valueHasMutated();
                *     }
                * }
                * self.gridReload();
                * notifications.statuses(currentStatuses);
                */
                var allStatuses = bulkRequests.all_statuses;
                notifications.statuses(allStatuses);
                notifications.statuses.valueHasMutated();
            } else {
                console.log('No bulk requests found');
            }
        },

        /**
         * Process bulk transfer requests
         */
        processTransferRequests: function (transferRequests) {
            var transferStatuses = transferRequests;

            for (var i = 0; i < transferStatuses.length; i++) {
                let queueIds = transferStatuses[i].queue_ids ?? [];
                let completedIds = transferStatuses[i].completed_ids ?? [];

                notifications.updateTransferStatus(
                    [...queueIds, ...completedIds],
                    transferStatuses[i].user,
                    queueIds,
                    completedIds,
                    transferStatuses[i].status,
                    transferStatuses[i]
                );
            }
        },

        /**
         * Reload grid.
         *
         * @returns {void}
         */
        gridReload: function () {
            var showLoader = false;
            var debounce = true;

            reloadGrid.reloadUIComponent(
                'wtai_products_grid_listing.wtai_products_grid_listing_data_source',
                showLoader,
                debounce
            );
        }
    });
});
