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

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

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

    return Component.extend({
        defaults: {
            productId: '',
            storeId: '',
            connection: null,
            retryReconnectAttempt: 0,
            enableMaxRetryAttempt: true,
            maxRetryAttempt: 3,
            accessToken: '',
            signalRUrl: '',
            singleGenerate: 0,
            fieldStatus: 2,
            optimization: 4,
            snapshot: 5,
            openAnalysis: 6,
            transfer: 8,
            bulkGenerate: 1,
            creditsUpdate: 99,
            message: $t('Text generation completed'),
            showMessage: false,
            messageSuccess: false,
            successMessage: $t('Text generation completed'),
            invalidImagePopupDoNotShowAgain: false,
            pageTitle: '',
            pageDescription: '',
            categoryDescription: '',
            errorMessage: '',
            statusFailed: 'Failed',
            statusCompleted: 'Completed',
            userName: '',
            
            /** SignalR Connection Status Constants */
            connectionStatusDisconnected: 'Disconnected',
            connectionStatusConnecting: 'Connecting',
            connectionStatusConnected: 'Connected',
            connectionStatusReconnecting: 'Reconnecting',
            connectionStatusFailed: 'Failed'
        },

        isGettingNewToken: false,
        gettingData: [],
        isFetchingGeneratedData: false,

        reconnectStopped: false,

        bulkKoGridReloadSnapshots: {},

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

            self = this;

            try {
                notifications.setScope('Category');
            } catch (e) {
                // no-op
            }
            
            /** Initialize connection status in model */
            this.updateConnectionStatus(this.connectionStatusDisconnected, '', false, 'Not initialized');

            this.connectTostream();

            try {
                var initialCategory = editData.currentCategory();
                if (initialCategory && typeof initialCategory.categoryId !== 'undefined') {
                    self.productId(initialCategory.categoryId);
                }
                if (initialCategory && typeof initialCategory.storeId !== 'undefined') {
                    self.storeId(initialCategory.storeId);
                }
            } catch (e) {
                // no-op
            }

            editData.currentCategory.subscribe(function (currentCategory) {
                if (!currentCategory) {
                    return;
                }

                if (typeof currentCategory.categoryId !== 'undefined') {
                    self.productId(currentCategory.categoryId);
                }

                if (typeof currentCategory.storeId !== 'undefined') {
                    self.storeId(currentCategory.storeId);
                }
            });

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

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

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

            signalRModel.isConnected.subscribe(function (isConnected) {
                if (isConnected) {
                    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',
                'userName',
                'message',
                'showMessage',
                'messageSuccess',
                'errorMessage'
            ]);

            this.generateComplete = ko.computed(function () {
                var fieldsGenerated = signalRModel.fieldsGenerated(),
                    fieldsValues = Object.values(fieldsGenerated),
                    hasTrue = fieldsValues.includes(true),
                    hasFalse = fieldsValues.includes(false);

                var isGenerating = signalRModel.generating(),
                    isTextsGenerating = hasTrue || hasFalse,
                    isTextsGenerated = hasTrue && !hasFalse;

                if (isGenerating && isTextsGenerating && isTextsGenerated) {
                    return true;
                }

                return false;
            });

            this.generateComplete.subscribe(function (generateComplete) {
                if (generateComplete) {
                    /**self.catchFieldsError();
                     *self.getGenerated();
                     */
                }
                if (keywordAnalysis.refreshingData()) {
                    keywordAnalysis.refreshingData(false);
                    signalrUtils.getCredits(self);
                    self.getOptimization();
                    self.gridReload();
                }
            });

            return this;
        },

        /**
         * Fetch bulk requests via AJAX.
         *
         * @returns {void}
         */
        fetchBulkRequests: function () {
            if (!self.getBulkRequestsUrl || !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 && response.success) {
                        /*remove all non-complete statuses before processing*/
                        notifications.statuses([]);
                        self.processBulkRequests(response.bulk_requests);
                        /** reload grid */
                        self.gridReload();
                    } else if (response) {
                        console.error('Error fetching bulk requests:', response.message);
                    } else {
                        console.error('Error fetching bulk requests: empty response');
                    }
                },
                error: function (xhr, status, error) {
                    if (status !== 'abort') {
                        console.error('AJAX error fetching bulk requests:', error);
                    }
                }
            });
        },

        /**
         * Process fetched bulk requests.
         *
         * @param {Array|Object} bulkRequests
         * @returns {void}
         */
        processBulkRequests: function (bulkRequests) {
            if (bulkRequests && bulkRequests.all_statuses && bulkRequests.all_statuses.length > 0) {
                var allStatuses = bulkRequests.all_statuses;
                if (typeof notifications.restoreStatusesFromBulkRequests === 'function') {
                    notifications.restoreStatusesFromBulkRequests(allStatuses);
                } else {
                    notifications.statuses(allStatuses);
                    notifications.opened(false);
                }
            } else {
                console.log('No bulk requests found');
            }
        },
        
        /**
         * Handle returning from idle state to sync generated texts.
         */
        handleUserReturn: function () {
            if (signalRModel.toGetGenerated() && !self.isFetchingGeneratedData) {
                self.catchFieldsError();
                self.getGenerated();
            }
        },

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

        /**
         * Catch fields error.
         */
        catchFieldsError: function () {
            self.errorMessage('');
            self.messageSuccess(true);
            self.getNormalTextsError();
        },

        /**
         * 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 () {
            if (this.invalidImagePopupDoNotShowAgain || window.wtaiInvalidImagePopupDoNotShowAgain) {
                return '';
            }

            var subMessage = $t('Image analysis will be skipped for categories or representative products with invalid images, and image descriptions may be missing.'),
                imagesHtml = $t('Error encountered while uploading some images.%s %s')
                    .replace('%s', '<ul></ul>')
                    .replace('%s', '<span class="wtai-submessage">' + subMessage + '</span>');

            if (invalidImageModel.invalidImages().length > 0) {
                invalidImageModel.invalidImages().forEach(element => {
                    imagesHtml += '<img src="' + element + '">';
                });

                return imagesHtml;
            }
            return '';
        },

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

        /**
         * Connect to signalR stream.
         */
        connectTostream: function () {
            if (self.reconnectStopped) {
                return;
            }
            if (self.accessToken()) {
                /** Update connection status to Connecting */
                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"
                    ) {
                        if (self.reconnectStopped) {
                            return;
                        }
                        self.updateConnectionStatus(self.connectionStatusReconnecting, self.connection.connectionState, false, null);
                        self.connection.start();
                    }
                });
            } else {
                self.updateConnectionStatus(self.connectionStatusDisconnected, '', false, 'No access token available');
            }
        },

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

        /**
         * Connection error actions.
         *
         * @param {object} error
         * @returns {void}
         */
        onConnectionError: function (error) {
            if (self.reconnectStopped) {
                return;
            }
            var doConnect = true;
            var errorMessage = error && error.message ? error.message : 'Connection error';
            
            /** Update connection status to Disconnected with error */
            self.updateConnectionStatus(self.connectionStatusDisconnected, '', false, errorMessage);

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

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

                require([
                    'WriteTextAI_WriteTextAI/js/model/grid/popups/image-upload-progressbar',
                    'WriteTextAI_WriteTextAI/js/model/edit/error-messages'
                ], function (imageUploadProgressbarModel, errorMessagesModel) {
                    try {
                        imageUploadProgressbarModel.toCancel(true);
                        imageUploadProgressbarModel.visible(false);
                    } catch (e) {
                        // no-op
                    }

                    try {
                        $('body').notification('clear');
                        errorMessagesModel.messages([
                            $t('Please check your internet connection and try again after reloading the page. If the issue persists, please contact our support team at support@writetext.ai.')
                        ]);
                        /** Reset generation state if stuck due to connection interruption */
                        if (signalRModel.generating() === true) {
                            /** Check if text generation is actually completed */
                            var fieldsGenerated = signalRModel.fieldsGenerated(),
                                fieldsValues = Object.values(fieldsGenerated),
                                hasTrue = fieldsValues.includes(true),
                                hasFalse = fieldsValues.includes(false),
                                isTextsGenerated = hasTrue && !hasFalse;

                            /** Only reset if generation is stuck (not properly completed) */
                            if (!isTextsGenerated) {
                                signalRModel.generating(false);
                                signalRModel.editTitleStatusText('');
                                signalRModel.editTitleStatusText.valueHasMutated();
                            }
                        }
                    } catch (e) {
                        // no-op
                    }
                });
            }

            if (doConnect) {
                /** Update retry attempts in model */
                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++;
            }
        },

        /**
         * Get new access token.
         */
        getNewAccessToken: function () {
            var url = self.getNewAccessTokenUrl;
        
            self.isGettingNewToken = true;
            $.ajax({
                url: url,
                type: 'GET',
                dataType: 'json',
                showLoader: false,
                success: function (response) {
                    if (response.success) {
                        self.accessToken(response.access_token);
                    }
                }
            }).always(function () {
                self.isGettingNewToken = false;
            });
        },

        /**
         * 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('Categories 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);
            
            /** Monitor connection state changes */
            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('Categories 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)
            };
        },

        /**
         * Update text fields.
         *
         * @param {String} field
         * @param {String} value
         *
         * @returns {void}
         */
        updateField: function (field, value) {
            switch (field) {
                case self.pageTitle:
                    textfields.pageTitle(value);
                    break;
                case self.pageDescription:
                    textfields.pageDescription(value);
                    break;
                case self.categoryDescription:
                    /** textfields.categoryDescription(value); */
                    break;
            }
        },

        /**
         * Get text field value.
         *
         * @param {String} field
         * @returns {String}
         */
        getField: function (field) {
            var value = '';

            switch (field) {
                case self.pageTitle:
                    value = textfields.pageTitle();
                    break;
                case self.pageDescription:
                    value = textfields.pageDescription();
                    break;
                case self.categoryDescription:
                    value = textfields.categoryDescription();
                    break;
            }

            return value;
        },

        /**
         * Get index field.
         *
         * @param {String} field
         * @returns {String}
         */
        getIndexField: function (field) {
            var value = '';

            switch (field) {
                case self.pageTitle:
                    value = 'page_title';
                    break;
                case self.pageDescription:
                    value = 'page_description';
                    break;
                case self.categoryDescription:
                    value = 'category_description';
                    break;
            }

            return value;
        },

        /**
         * 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 = {},
                streamSubType = messageEntry.encodedMsg.subType,
                productId = self.productId(),
                storeId = Number(self.storeId()),
                entityType = self.entityType;

            var currentUserName = '';
            try {
                currentUserName = typeof self.userName === 'function' ? String(self.userName() || '') : '';
            } catch (e) {
                currentUserName = '';
            }

            console.log(messageEntry.encodedMsg);
            
            switch (streamType) {
                case self.creditsUpdate:
                    try {
                        signalrUtils.getCredits(self);
                    } catch (e) {
                        // no-op
                    }
                    break;
                case self.bulkGenerate:
                    if (streamRecordType === 'Product') {
                        break;
                    }

                    if (
                        currentUserName &&
                        streamUserName &&
                        streamUserName !== currentUserName &&
                        ['Completed', 'Cancelled', 'Failed', 'TimedOut'].includes(streamStatus)
                    ) {
                        try {
                            var idsToRemoveForOtherUser = [
                                ...(streamQueuedIds || []),
                                ...(streamRunningIds || []),
                                ...(streamFailedIds || []),
                                ...(streamFailedTransferIds || []),
                                ...(streamCompletedIds || [])
                            ];

                            if (streamRecordId !== undefined && streamRecordId !== null) {
                                idsToRemoveForOtherUser.push(streamRecordId);
                            }

                            idsToRemoveForOtherUser = [...new Set(idsToRemoveForOtherUser)];

                            if (idsToRemoveForOtherUser.length) {
                                var currentIdentifiersForOtherUser = currentlyGeneratingModel.recordIdentifiers();
                                idsToRemoveForOtherUser.forEach((id) => {
                                    var idIdentifier = entityType + '_' + id + '_' + streamStoreId;
                                    var idx = currentIdentifiersForOtherUser.indexOf(idIdentifier);
                                    if (idx > -1) {
                                        currentIdentifiersForOtherUser.splice(idx, 1);
                                    }
                                });
                                currentlyGeneratingModel.recordIdentifiers(currentIdentifiersForOtherUser);
                            }

                            notifications.statuses.remove(function (status) {
                                return status && status.id === messageEntry.encodedMsg.id;
                            });

                            var hasCurrentUserInProgress = notifications.statuses().some(function (status) {
                                if (!status) {
                                    return false;
                                }
                                if (String(status.recordType || '').toLowerCase() !== 'category') {
                                    return false;
                                }
                                if (status.user !== currentUserName) {
                                    return false;
                                }

                                return !['Completed', 'Cancelled', 'Failed', 'TimedOut'].includes(status.status);
                            });
                            if (!hasCurrentUserInProgress) {
                                notifications.opened(false);
                                if (['Cancelled', 'Completed', 'Failed', 'TimedOut'].includes(streamStatus)) {
                                    if (signalRModel.generating() === true) {
                                        signalRModel.generating(false);
                                    }
                                    signalRModel.editTitleStatusText('');
                                    signalRModel.editTitleStatusText.valueHasMutated();
                                }
                            }
                        } catch (e) {
                            // no-op
                        }

                        self.gridReload();

                        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 = messageEntry.encodedMsg.queuedIds;
                                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);
                                        
                                        /** Set queuing status for the current product */
                                        if (storeId === streamStoreId && id === productId) {
                                            signalRModel.editTitleStatusText($t('Queued for text generation...'));
                                        }
                                    }
                                });

                                var isFromBulk = notifications.currentBulkRequestId() === notifications.tempId;
                                if ( isFromBulk && streamSubType === 1) {
                                    notifications.addStreamStatus(messageEntry);
                                }

                                if (messageEntry.encodedMsg.subType === 0 &&
                                    storeId === streamStoreId &&
                                    currentlyGeneratingModel.recordIdentifiers().includes(
                                        entityType + '_' + productId + '_' + streamStoreId
                                    )
                                ) {
                                    if (messageEntry.encodedMsg.status === 'Pending') {
                                        signalRModel.editTitleStatusText($t('Queued for text generation...'));
                                    } else if (messageEntry.encodedMsg.status === 'Running') {
                                        signalRModel.editTitleStatusText($t('Generating text...'));
                                    }
                                }
                                
                                /** Also handle initial queuing for the current product */
                                if (storeId === streamStoreId &&
                                    streamQueuedIds.includes(productId) &&
                                    currentlyGeneratingModel.recordIdentifiers().includes(
                                        entityType + '_' + productId + '_' + streamStoreId
                                    )
                                ) {
                                    signalRModel.editTitleStatusText($t('Queued for text generation...'));
                                }
                                
                                keywordsProgressModel.addOrUpdateStatus(messageEntry.encodedMsg);


                                if (messageEntry.encodedMsg.status === 'Failed' || messageEntry.encodedMsg.status === 'Completed'
                                    || messageEntry.encodedMsg.status === 'Cancelled' || messageEntry.encodedMsg.status === 'TimedOut'
                                ) {
                                    if (signalRModel.generating() === true &&
                                        storeId === streamStoreId &&
                                        (streamCompletedIds.includes(productId) ||
                                        streamFailedIds.includes(productId) ||
                                        streamRunningIds.includes(productId)) &&
                                        requestCompleted !== 0
                                    ) {
                                        self.catchFieldsError();
                                        self.getGenerated();
                                    }

                                    if (
                                        streamStatus === 'Completed' &&
                                        editData.opened &&
                                        typeof editData.opened === 'function' &&
                                        editData.opened() &&
                                        storeId === streamStoreId &&
                                        streamCompletedIds.includes(productId) &&
                                        signalRModel.generating() === false
                                    ) {
                                        self.repopulateTextFields();
                                    }

                                    if (['Cancelled', 'Completed', 'Failed', 'TimedOut'].includes(streamStatus)) {
                                        /**
                                         * Ensure we clear out any lingering identifiers for this bulk request
                                         * (especially important across multiple browsers/tabs).
                                         */
                                        var idsToRemove = [
                                            ...(streamQueuedIds || []),
                                            ...(streamRunningIds || []),
                                            ...(streamFailedIds || []),
                                            ...(streamFailedTransferIds || []),
                                            ...(streamCompletedIds || [])
                                        ];

                                        idsToRemove = [...new Set(idsToRemove)];

                                        if (idsToRemove.length) {
                                            var currentIdentifiers = currentlyGeneratingModel.recordIdentifiers();
                                            idsToRemove.forEach((id) => {
                                                var idIdentifier = entityType + '_' + id + '_' + streamStoreId;
                                                var idx = currentIdentifiers.indexOf(idIdentifier);
                                                if (idx > -1) {
                                                    currentIdentifiers.splice(idx, 1);
                                                }
                                            });
                                            currentlyGeneratingModel.recordIdentifiers(currentIdentifiers);
                                        }
                                    }
                                    if (['Cancelled', 'Completed', 'Failed', 'TimedOut'].includes(streamStatus)) {
                                        /**
                                         * If this admin session has no more generating entities, clear the sticky
                                         * edit page status text (e.g. "Generating text...") for all browsers/tabs.
                                         */
                                        if (currentlyGeneratingModel.recordIdentifiers().length === 0) {
                                            if (signalRModel.generating() === true) {
                                                signalRModel.generating(false);
                                            }
                                            signalRModel.editTitleStatusText('');
                                            signalRModel.editTitleStatusText.valueHasMutated();
                                        }
                                    }
                                }
                            } else {
                                notifications.addStreamStatus(messageEntry);
                            }

                            if (messageEntry.encodedMsg.status === 'Cancelled'
                                || messageEntry.encodedMsg.status === 'Completed'
                                || messageEntry.encodedMsg.status === 'Failed'
                            ) {
                                /** Close progress bar if current user has no more generating items */
                                if (signalRModel.generating() === true &&
                                    currentlyGeneratingModel.recordIdentifiers().length === 0) {
                                    signalRModel.generating(false);
                                    signalRModel.editTitleStatusText('');
                                    signalRModel.editTitleStatusText.valueHasMutated();
                                }
                            }

                            if (['Cancelled', 'Completed', 'Failed', 'TimedOut'].includes(streamStatus)) {
                                try {
                                    self.gridReload();
                                } catch (e) {
                                    // no-op
                                }
                            }
                            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 {
                                    if (!(messageEntry.encodedMsg.total === 1 && editData.opened())) {
                                        notifications.addStreamStatus(messageEntry);
                                    }
                                }
                                
                                keywordsProgressModel.addOrUpdateStatus(messageEntry.encodedMsg);
                            } else {
                                /** Use the model's method to add or update status */
                                keywordsProgressModel.addOrUpdateStatus(messageEntry.encodedMsg);
                            }
                            self.reloadGridForBulkKoProgress(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;
                            }
                            if (!(messageEntry.encodedMsg.total === 1 && editData.opened())) {
                                notifications.addStreamStatus(messageEntry);
                            }
                            
                            if (['Completed', 'Cancelled', 'Failed'].includes(streamStatus)) {
                                //remove status
                                if (streamUserName !== self.userName()) {
                                    notifications.removeTransferStatus(streamUserName);
                                }
                                self.gridReload();
                            }
                            break;
                        default:
                            break;
                    }
                    break;
                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 = self.getIndexField(streamFieldType);
        
                        fieldsGenerated = signalRModel.fieldsGenerated();
                        fieldsGenerated[indexField] = true;
                        signalRModel.fieldsGenerated(fieldsGenerated);
                        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 'category_description':
                                $('.wtai-category-description-loading-mask').removeClass('wtai-show');
                                statuses['categoryDescriptionGenerateStatus'] = true;
                                break;
                        }
                        textfields.statuses(statuses);
                    }
                    break;
                case self.singleGenerate:
                case self.optimization:
                    if (streamRecordType === 'Product') {
                        break;
                    }
                    var statuses = keywordAnalysis.statuses(),
                        storeId = Number(messageEntry.encodedMsg.storeId),
                        recordId = messageEntry.encodedMsg.recordId,
                        existingIndex = -1;

                    statuses.forEach(function (status, index) {
                        if (Number(status.storeId) === Number(storeId) && status.recordId === recordId) {
                            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'); */
                    break;
                case self.transfer:
                    if (['Completed', 'Failed', 'Cancelled', 'TimedOut'].includes(streamStatus)
                        && streamRecordType === entityType
                    ) {
                        signalrUtils.getCredits(self);
                        self.gridReload();
                    }

                    if (
                        streamStatus === 'Completed' &&
                        editData.opened &&
                        typeof editData.opened === 'function' &&
                        editData.opened() &&
                        storeId === streamStoreId &&
                        streamCompletedIds.includes(productId) &&
                        signalRModel.generating() === false
                    ) {
                        self.repopulateTextFields();
                    }

                    if (streamRecordType === entityType) {
                        notifications.addStreamStatus(messageEntry);
                    }
                    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 = self.getField(streamFieldType),
                newValue = currentValue + streamPartialText;

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

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

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

            self.isFetchingGeneratedData = true;

            $.ajax({
                url: url,
                type: 'POST',
                data: {
                    category_id: productId,
                    store_id: storeId
                },
                dataType: 'json',
                showWriteTextAILoader: true,
                showPageTitleLoader: selectedFields.includes('page_title'),
                showPageDescriptionLoader: selectedFields.includes('page_description'),
                showCategoryDescriptionLoader: selectedFields.includes('category_description'),
                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() &&
                        keywordAnalysis.optimizingStoreId() === self.storeId()
                    ) {
                        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);
            });
        },

        normalizeBulkKoIds: function (ids) {
            return (Array.isArray(ids) ? ids : []).map(function (id) {
                return String(id);
            }).sort();
        },

        getBulkKoProcessedCount: function (status) {
            var completedIdsCount = Array.isArray(status.completedIds) ? status.completedIds.length : 0;

            if (completedIdsCount > 0) {
                return completedIdsCount;
            }

            return Number(status.completed || 0);
        },

        getBulkKoProgressSnapshot: function (status) {
            return JSON.stringify({
                processed: this.getBulkKoProcessedCount(status),
                completed: Number(status.completed || 0),
                failed: Number(status.failed || 0),
                percent: Number(status.percent || 0),
                completedIds: this.normalizeBulkKoIds(status.completedIds),
                failedIds: this.normalizeBulkKoIds(status.failedIds),
                skippedIds: this.normalizeBulkKoIds(status.skippedIds)
            });
        },

        reloadGridForBulkKoProgress: function (status) {
            var requestId,
                entityType = String(this.entityType || '').toLowerCase(),
                recordType = String(status && status.recordType ? status.recordType : '').toLowerCase(),
                processedCount,
                snapshot,
                previousSnapshot;

            if (!status ||
                Number(status.subType) !== 2 ||
                Number(status.total || 0) <= 1 ||
                (recordType && recordType !== entityType) ||
                !['Preparing', 'Pending', 'Running', 'Cancelling'].includes(status.status)) {
                return;
            }

            requestId = String(status.id || '');

            if (!requestId) {
                return;
            }

            processedCount = this.getBulkKoProcessedCount(status);
            snapshot = this.getBulkKoProgressSnapshot(status);
            previousSnapshot = this.bulkKoGridReloadSnapshots[requestId];
            this.bulkKoGridReloadSnapshots[requestId] = snapshot;

            if (!processedCount || previousSnapshot === snapshot) {
                return;
            }

            this.gridReload();
        },

        /**
         * Repopulate text fields.
         */
        repopulateTextFields: function () {
            var defaultSelectedFields = signalRModel.selectedFields();
            var url = self.getGeneratedUrl,
                statuses = textfields.statuses(),
                productId = self.productId(),
                storeId = self.storeId(),
                
                selectedFields = [
                    'page_title',
                    'page_description',
                    'category_description'
                ];

            if (self.gettingData['repopulateTextFieldsCategory'] !== undefined ||
                self.gettingData['repopulateTextFieldsCategory']) {
                self.gettingData['repopulateTextFieldsCategory'].abort();
            }

            self.gettingData['repopulateTextFieldsCategory'] = $.ajax({
                url: url,
                type: 'POST',
                data: {
                    category_id: productId,
                    store_id: storeId
                },
                dataType: 'json',
                showWriteTextAILoader: true,
                showPageTitleLoader: selectedFields.includes('page_title'),
                showPageDescriptionLoader: selectedFields.includes('page_description'),
                showCategoryDescriptionLoader: selectedFields.includes('category_description'),
                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 {Object} response
         * @param {Array} selectedFields
         *
         * @returns {void}
         */
        updateTextFields: function (response, selectedFields) {
            var statuses = textfields.statuses();
            selectedFields.forEach((field) => {
                switch (field) {
                    case 'page_title':
                        if (response.generated_texts.page_title !== undefined) {
                            textfields.pageTitle(response.generated_texts.page_title);
                            textfields.originalPageTitle(response.generated_texts.page_title);
                            statuses['pageTitleGenerateStatus'] = true;
                            statuses['pageTitleTransferStatus'] = false;
                            $('#wtaiPageTitleMask').removeClass('wtai-generated');
                            $('#wtaiPageTitleMask').addClass('wtai-generated');
                        }
                        break;
                    case 'page_description':
                        if (response.generated_texts.page_description !== undefined) {
                            textfields.pageDescription(response.generated_texts.page_description);
                            textfields.originalPageDescription(response.generated_texts.page_description);
                            statuses['pageDescriptionGenerateStatus'] = true;
                            statuses['pageDescriptionTransferStatus'] = false;
                            $('#wtaiPageDescMask').removeClass('wtai-generated');
                            $('#wtaiPageDescMask').addClass('wtai-generated');
                        }
                        break;
                    case 'category_description':
                        if (response.generated_texts.category_description !== undefined) {
                            textfields.categoryDescription(response.generated_texts.category_description);
                            textfields.originalCategoryDescription(response.generated_texts.category_description);
                            statuses['categoryDescriptionGenerateStatus'] = true;
                            statuses['categoryDescriptionTransferStatus'] = false;
                            $('#wtaiCategoryDescMask + .mce-tinymce').removeClass('wtai-generated');
                            $('#wtaiCategoryDescMask + .mce-tinymce').addClass('wtai-generated');
                            $('#wtaiCategoryDescMask + .tox-tinymce').removeClass('wtai-generated');
                            $('#wtaiCategoryDescMask + .tox-tinymce').addClass('wtai-generated');
                            $('#wtaiCategoryDescMask + .tox-hugerte').removeClass('wtai-generated');
                            $('#wtaiCategoryDescMask + .tox-hugerte').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);
            });
        },

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

            reloadGrid.reloadUIComponent(
                'wtai_categories_grid_listing.wtai_categories_grid_listing_data_source',
                showLoader,
                debounce
            );
        }
    });
});
