/**
 * TalkToWebsite admin JS scripts
 *
 * @link   https://talktowebsite.com/
 * @file   This file contains admin-side JS scripts designed to run the TalkToWebsite AI plugin.
 * @author Vitalii A. Popov
 * @since  1.2.0
 */

/* jshint esversion: 6 */
/* jshint strict: true */
/* jshint bitwise: false */
/* globals ajaxurl, vappv_ttw_admin_js, jQuery, crypto, talkToWebsiteAdminLocalization */

( function ( $ ) {

	"use strict";

	// Set global "WP" variable
	window.wp = window.wp || {};

	// Set global "Localization" variable
	let talkToWebsiteAdminLocalization = window.talkToWebsiteAdminLocalization || {};

	// TTW admin scripts
	let talkToWebsiteAdmin = {

		// Is training tab inner content loaded once
		isTrainingTabLoadedFirstTime: false,
		
		// Is testing tab inner content loaded once
		isTestingTabLoadedFirstTime: false,

		// Is dialogues (history) tab loaded once
		isDialoguesTabLoadedFirstTime: false,

		// Is logs tab inner content loaded once
		isLogsTabLoadedFirstTime: false,

		// Mutex to prevent messages multi sending
		sendChatMessageMutex: false,

		// Is the webchat contains more messages in backward scrolling direction (from newest to oldest messages)
		areMoreMessagesInBackwardDirection: true,

		// Admin chat messages loading mutex (true | false)
		adminChatMessagesLoadingMutex: false,

		// Admin dialogue messages cache
		adminMessagesCache: [],

		// Link to l10n container
		$ttwMessages: null,

		/* --------------------------------------------------------------------------------- */

		/**
		 * Check is value empty or equals to null
		 * @param {Object} value Value to check
		 * @returns {boolean} Check results
		 */
		isNullOrEmpty: function( value ) {
			if ( value ) {
				return false;
			}
			else {
				return true;
			}
		},

		/**
		 * Create new GUID string
		 * @returns {string}
		 */
		generateUUIDv4: function () {
			return ( [1e7] + -1e3 + -4e3 + -8e3 + -1e11 ).replace( /[018]/g, c =>
				( c ^ crypto.getRandomValues( new Uint8Array( 1 ) )[ 0 ] & 15 >> c / 4 ).toString( 16 )
			);
		},

		/**
		 * Process specified time part / prepend leading '0' to number if necessary (from x to [?0]x)
		 * @param {number} timePart
		 * @returns {string|*}
		 */
		processTimePart: function( timePart ) {
			if ( timePart ) {
				if ( timePart.toString().length === 1 ) {
					return `0${timePart}`;
				} else {
					return timePart.toString();
				}
			}
			else if ( timePart === 0 ) {
				return '00';
			}
			else {
				return '';
			}
		},

		/**
		 * Format date in YYYY-MM-DD style
		 * @param {Date} date
		 * @returns {string|*}
		 */
		formatDateYYYYMMDD: function( date ) {
			if ( ! date ) {
				return date;
			}
			else if ( Object.prototype.toString.call( date ) !== '[object Date]' ) {
				return date.toString();
			}
			else {
				return date.getFullYear() + '-' + talkToWebsiteAdmin.processTimePart( date.getMonth() + 1 ) + '-' + talkToWebsiteAdmin.processTimePart( date.getDate() );
			}
		},

		/**
		 * Format date in YYYY-MM-DD HH:mm:ss style
		 * @param {Date} date Date object
		 * @returns {string|*}
		 */
		formatDateYYYYMMDD_HHMMSS: function( date ) {
			if ( ! date ) {
				return date;
			}
			else if ( Object.prototype.toString.call( date ) !== '[object Date]' ) {
				return date.toString();
			}
			else {
				return talkToWebsiteAdmin.formatDateYYYYMMDD( date ) + ' ' + talkToWebsiteAdmin.processTimePart( date.getHours() ) + ':' + talkToWebsiteAdmin.processTimePart( date.getMinutes() ) + ':' + talkToWebsiteAdmin.processTimePart( date.getSeconds() );
			}
		},

		/**
		 * Format date in YYYY-MM-DD HH:mm:ss style
		 * @param {Date} date
		 * @returns {string|*}
		 */
		formatDateYYYYMMDD_comma_HHMM: function( date ) {
			if ( ! date ) {
				return date;
			}
			else if ( Object.prototype.toString.call( date ) !== '[object Date]' ) {
				return date.toString();
			}
			else {
				return talkToWebsiteAdmin.formatDateYYYYMMDD( date ) + ', ' + talkToWebsiteAdmin.processTimePart( date.getHours() ) + ':' + talkToWebsiteAdmin.processTimePart( date.getMinutes() );
			}
		},

		/**
		 * Prepend item to the array
		 * @param {object} value Value to prepend
		 * @param {array} array Target array
		 * @returns
		 */
		arrayPrepend: function( value, array ) {
			let newArray = array.slice();
			newArray.unshift( value );
			return newArray;
		},

		/**
		 * Linkify plain text: replace all plain URLs to <a href> tags
		 * @param {string} inputText
		 * @returns {string}
		 */
		linkifyPlainText: function( inputText ) {
			let replacedText, replacePattern1, replacePattern2, replacePattern3;

			//URLs starting with http://, https://, or ftp://
			replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
			replacedText = inputText.replace( replacePattern1, '<a href="$1" target="_blank" data-role="ttw-linkifyed-url">$1</a>' );

			//URLs starting with "www." (without // before it, or it'd re-link the ones done above).
			replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
			replacedText = replacedText.replace( replacePattern2, '$1<a href="http://$2" target="_blank" data-role="ttw-linkifyed-url">$2</a>' );

			//Change email addresses to mailto:: links.
			replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
			replacedText = replacedText.replace( replacePattern3, '<a href="mailto:$1" data-role="ttw-linkifyed-url">$1</a>' );

			return replacedText;
		},

		/**
		 * Add markup to the text (chat **text** to <b>text</b>, ~~text~~ to <i>text</i> etc.)
		 * @param {string} inputText
		 * @returns {string}
		 */
		markupPlainText: function( inputText ) {
			if ( inputText ) {
				let inputTextWithMarkup = inputText.replace( /\*\*(.*?)\*\*/g, "<b>$1</b>" );
				inputTextWithMarkup = inputTextWithMarkup.replace( /__(.*?)__/g, "<u>$1</u>" );
				inputTextWithMarkup = inputTextWithMarkup.replace( /~~(.*?)~~/g, "<i>$1</i>" );
				inputTextWithMarkup = inputTextWithMarkup.replace( /<<(.*?)>>/g, "<a href='$1'>Link</a>" );
				return inputTextWithMarkup;
			}
			else {
				return '';
			}
		},

		/**
		 * Prepare message text to drawing
		 * @param {string} inputText
		 * @returns {string}
		 */
		prepareMessageText: function( inputText ) {
			if ( inputText ) {
				//Linkify text
				let inputTextPrepared = talkToWebsiteAdmin.linkifyPlainText( inputText );
				inputTextPrepared = inputTextPrepared.replaceAll( '</a>.;', '</a> ;' );
				inputTextPrepared = inputTextPrepared.replaceAll( '</a>.', '</a> ;' );

				//Replace newlines to <br /><br />
				inputTextPrepared = inputTextPrepared.replace( /(?:\r\n|\r|\n)/g, '<br /><br />' );

				//Replace long newline sequences
				while ( inputTextPrepared.indexOf( '<br /><br /><br />' ) !== -1 ) {
					inputTextPrepared = inputTextPrepared.replaceAll( '<br /><br /><br />', '<br /><br />' );
				}

				//Replace ** to <b>
				inputTextPrepared = talkToWebsiteAdmin.markupPlainText( inputTextPrepared );

				return inputTextPrepared;
			}
			else {
				return '';
			}
		},

		/* --------------------------------------------------------------------------------- */

		/**
		 * Initialize TTW plugin admin section
		 */
		initializeAdmin: function () {

			// Create link to the messages container
			talkToWebsiteAdminLocalization = window.talkToWebsiteAdminLocalization;

			// Subscribe tabs clicks (switching plugin main tabs)
			$( '#ttw-tabs-headings' ).find( '[data-role="ttw-tab"]' ).off( 'click' ).on( 'click', function( e ) {
				let tabId = $( this ).attr( 'data-tabid' );
				
				$( '#ttw-tabs-headings' ).find( '[data-role="ttw-tab"]' ).removeClass( 'ttw-active' );
				$( this ).addClass( 'ttw-active' );

				$( '#ttw-tabs-content' ).find( '[data-role="ttw-tab-content"][data-level="1"]' ).hide();
				$( '#ttw-tabs-content' ).find( `[data-role="ttw-tab-content"][data-level="1"][data-tabid="${tabId}"]` ).show();

				// ------------------------------------------------------
				// Handle special behavior
				if ( tabId === 'training' ) {

					// Check is OpenAI API key set and settings are saved
					if ( $( '#ttw-is-chatgpt-key-set' ).val() === 'no' || $( '#ttw-are-plugin-settings-saved' ).val() === 'no' ) {
						return;
					}

					// Re-load articles list if tabbed first time
					if ( talkToWebsiteAdmin.isTrainingTabLoadedFirstTime === false ) {
						talkToWebsiteAdmin.trainingTabGetAndDrawAllContentFromTheWebsite();
						talkToWebsiteAdmin.isTrainingTabLoadedFirstTime = true;
					}
				}
				else if ( tabId === 'testing' ) {

					// Check is OpenAI API key set and settings are saved
					if ( $( '#ttw-is-chatgpt-key-set' ).val() === 'no' || $( '#ttw-are-plugin-settings-saved' ).val() === 'no' ) {
						return;
					}

					// Check is chat trained
					if ( $( '#ttw-is-assistant-assigned' ).val() === 'no' ) {
						return;
					}

					// Load chat between admin and ChatGPT
					if ( talkToWebsiteAdmin.isTestingTabLoadedFirstTime === false ) {
						talkToWebsiteAdmin.adminDialogueTabGetAndDrawAllAdminMessages();
						talkToWebsiteAdmin.isTestingTabLoadedFirstTime = true;
					}

					// Select input field annd scroll it into observable area
					$( '#ttw-dialogue-input-textarea' ).select().focus();
					$( 'html, body' ).animate( {
						scrollTop: $( '#ttw-dialogue-input-textarea' ).offset().top
					}, 2000 );
				}
				else if ( tabId === 'dialogues' ) {

					// Check is OpenAI API key set and settings are saved
					if ( $( '#ttw-is-chatgpt-key-set' ).val() === 'no' || $( '#ttw-are-plugin-settings-saved' ).val() === 'no' ) {
						return;
					}

					// Check is chat trained
					if ( $( '#ttw-is-assistant-assigned' ).val() === 'no' ) {
						return;
					}

					// Load dialogues list
					if ( talkToWebsiteAdmin.isDialoguesTabLoadedFirstTime === false ) {
						talkToWebsiteAdmin.historyTabGetAndDrawAllDialogues();
						talkToWebsiteAdmin.isDialoguesTabLoadedFirstTime = true;
					}
				}
				else if ( tabId === 'logs' ) {

					// Check is OpenAI API key set and settings are saved
					if ( $( '#ttw-is-chatgpt-key-set' ).val() === 'no' || $( '#ttw-are-plugin-settings-saved' ).val() === 'no' ) {
						return;
					}

					// Re-load logs
					if ( talkToWebsiteAdmin.isLogsTabLoadedFirstTime === false ) { 
						talkToWebsiteAdmin.logsTabGetAndDrawAllLogs();
						talkToWebsiteAdmin.isLogsTabLoadedFirstTime = true;
					}
				}
				//------------------------------------------------------
			} );

			// Handle click on settings subtabs
			$( '#ttw-settings-tabs-headings' ).find( '[data-role="ttw-tab"]' ).off( 'click' ).on( 'click', function( e ) {

				let tabId = $( this ).attr( 'data-tabid' );

				$( '#ttw-settings-tabs-headings' ).find( '[data-role="ttw-tab"]' ).removeClass( 'ttw-active' );
				$( '#ttw-settings-tabs-headings' ).find( `[data-role="ttw-tab"][data-tabid="${tabId}"]` ).addClass( 'ttw-active' );

				$( '#ttw-settings-tabs-content' ).find( '[data-role="ttw-settings-section"]' ).hide();
				$( '#ttw-settings-tabs-content' ).find( `[data-role="ttw-settings-section"][data-tabid="${tabId}"]` ).show();

				if ( tabId === 'chatgpt-connection-block' ) {
					$( '#ttw-save-all-settings' ).hide();
				}
				else {
					$( '#ttw-save-all-settings' ).show();
				}

			} );
 
			// Handle click on 'Setup the settings' (Button on the very first tab)
			$( '#ttw-setup-the-plugin' ).off( 'click' ).on( 'click', function( e ) {
				$( '#ttw-tabs-headings' ).find( '[data-role="ttw-tab"][data-tabid="settings"]' ).trigger( 'click' );
			} );

			// Handle click on 'API address enpoint' url
			$( '.ttw-endpoint-api-address-field' ).off( 'click' ).on( 'click', function(e) {
				let apiAddress = $( this ).text();
				$( '#ttw-settings-openai-api-address' ).val( apiAddress );
			} );

			// Subscribe 'Save ChatGPT API key and test connection' button
			$( '#ttw-save-chatgpt-api-key-and-test-connection' ).off( 'click' ).on( 'click', function( e ) {
				talkToWebsiteAdmin.settingsTabSaveOpenAIAPIKey();
			} );

			// Subscribe 'Save all settings' button click
			$( '#ttw-save-all-settings' ).off( 'click' ).on( 'click', function( e ) {
				talkToWebsiteAdmin.settingsTabSaveAllPluginSettings();
			} );

			// Subscribe 'List AI models' button click (system purpose only)
			$( '#ttw-list-ai-models' ).off( 'click' ).on( 'click', function( e ) {
				talkToWebsiteAdmin.settingsTabListAIModels();
			});

			// Check / Uncheck all materials into selected tab - checkbox itself
			$( '#check_uncheck_all_materials_input' ).off( 'click' ).on( 'click', function( e ) {
				e.stopPropagation();
				let checkState = $( this ).is( ':checked' );
				$( '#ttw-training-website-content [data-role="check-material-box"]' ).prop( 'checked', checkState).trigger( 'change' );
				talkToWebsiteAdmin.trainingTabRefreshCheckedMaterialsCounters();
			} );
 
			// Subscrube click on 'Refresh content' button (Training tab)
			$( '#ttw-refresh-training-content' ).off( 'click' ).on( 'click', function( e ) {
				talkToWebsiteAdmin.trainingTabGetAndDrawAllContentFromTheWebsite();
			} ); 

			// Handle click on 'Re-train on checked materials'
			$( '#ttw-make-training' ).off( 'click' ).on( 'click', function( e ) {
				talkToWebsiteAdmin.trainingTabMakeTraining();
			} );

			// Subscribe 'Send message' button on the 'Testing' tab
			$( '#ttw-dialogue-send-message-button' ).off( 'click' ).on( 'click', function( e ) {
				talkToWebsiteAdmin.adminDialogueTabSendChatMessage(); 
			} );

			// Subscribe 'Enter / Return' keypress on input
			$( '#ttw-dialogue-input-textarea' ).off( 'keyup' ).on( 'keyup', function( e ) {
				if ( e.key === 'Enter' ) {
					talkToWebsiteAdmin.adminDialogueTabSendChatMessage();
				}
			} );

			// Handle scroll event into 'Testing' tab
			$( '#ttw-dialogue-block' ).off( 'scroll' ).on( 'scroll', function( event ) {
				talkToWebsiteAdmin.adminDialogueTabHandleDialogueScrolled();
			} );

			// Handle click on 'Select bubble appearence type' button
			$( '#ttw-chat-bubbles-types-area' ).find( '[data-role="ttw-bubble-picker-button"], .ttw-chat-bubble-image' ).off( 'click' ).on( 'click', function( e ) {
				$( this ).closest( '.ttw-chat-bubble-type-selector' ).find( 'input' ).trigger( 'click' );
				e.stopPropagation();
				talkToWebsiteAdmin.settingsTabResetSelectedBubbleButtonAppearence();
			} );

			// Handle click on 'Select bubble appearence type' input inside button
			$( '#ttw-chat-bubbles-types-area' ).find( '[data-role="ttw-bubble-picker-button"] input' ).off( 'click' ).on( 'click', function( e ) {
				e.stopPropagation();
				talkToWebsiteAdmin.settingsTabResetSelectedBubbleButtonAppearence();
			} );

			// Handle click on 'Select AI character' button
			$( '#ttw-ai-characters-area' ).find( '[data-role="ttw-ai-character-picker-button"], .ttw-ai-character-image, .ttw-ai-character-name-and-description' ).off( 'click' ).on( 'click', function( e ) {
				$( this ).closest( '.ttw-ai-character-type-selector' ).find( 'input' ).trigger( 'click' );
				e.stopPropagation();
				talkToWebsiteAdmin.settingsTabResetSelectedAICharacter();
			} );

			// Handle click on 'Select AI character' input inside button
			$( '#ttw-ai-characters-area' ).find( '[data-role="ttw-ai-character-picker-button"] input' ).off( 'click' ).on( 'click', function( e ) {
				e.stopPropagation();
				talkToWebsiteAdmin.settingsTabResetSelectedAICharacter();
			} );

			// Handle click on 'Enable plugin' button
			$( '#ttw-enable-plugin-from-notification-bar' ).off( 'click' ).on( 'click', function( e ) {
				talkToWebsiteAdmin.quickActionsEnablePlugin();
			} );

			// Handle click on 'Disable plugin' button
			$( '#ttw-disable-plugin-from-notification-bar' ).off( 'click' ).on( 'click', function( e ) {
				talkToWebsiteAdmin.quickActionsDisablePlugin();
			} );

			// Handle change limits slider
			$( '#ttw-settings-sugar-limits-summary' ).off( 'change' ).on( 'change', function( e ) {
				talkToWebsiteAdmin.handleLimitsMasterChanged();
			} );
			talkToWebsiteAdmin.handleLimitsMasterChanged();

			// Handle loader overlay positioning
			$( window ).resize( function () {
				// Resize / adjust 'Loading' modal
				if ( $( '#ttw-wait-loader-modal' ).is( ':visible' ) ) {
					talkToWebsiteAdmin.reiszeLoaderPositioning();
				}

				// Resize / adjust informational modals (info, errors etc.)
				if ( $( '.ttw-notification-container' ).length > 0 ) {
					talkToWebsiteAdmin.adjustModalWindowsPositioning();
				}
			} );
		},

		/* --------------------------------------------------------------------- */
		/*							  Settings tab							 */
		/* --------------------------------------------------------------------- */

		/**
		 * Handle change of the limits master input
		 */
		handleLimitsMasterChanged: function () {

			let limitsMasterCurrentValue = $( '#ttw-settings-sugar-limits-summary' ).val();
			limitsMasterCurrentValue = Number( limitsMasterCurrentValue );

			if ( 1 === limitsMasterCurrentValue ) {
				// Custom limits
				$( '#ttw-settings-dialogue-lifetime-minutes, #ttw-settings-max-characters-per-user-question, #ttw-settings-max-words-to-answer, #ttw-settings-max-input-words-per-day-by-all-users, #ttw-settings-max-input-characters-per-day-by-all-users, #ttw-settings-max-output-characters-per-day-by-ai, #ttw-settings-max-output-tokens-per-day-by-ai' ).removeAttr( 'readonly' ).removeClass( 'ttw-readonly' );
			}
			else {
				$( '#ttw-settings-dialogue-lifetime-minutes, #ttw-settings-max-characters-per-user-question, #ttw-settings-max-words-to-answer, #ttw-settings-max-input-words-per-day-by-all-users, #ttw-settings-max-input-characters-per-day-by-all-users, #ttw-settings-max-output-characters-per-day-by-ai, #ttw-settings-max-output-tokens-per-day-by-ai, #ttw-settings-dialogue-lifetime-max-messages' ).attr( 'readonly', 'readonly' ).addClass( 'ttw-readonly' );
				// Pre-defined limits
				switch( limitsMasterCurrentValue ) {
					case 2:
						// Small (<$5 / day)
						$( '#ttw-settings-dialogue-lifetime-minutes' ).val( 1440 );
						$( '#ttw-settings-max-characters-per-user-question' ).val( 200 );
						$( '#ttw-settings-max-words-to-answer' ).val( 100 );
						$( '#ttw-settings-max-input-words-per-day-by-all-users' ).val( 10000 );
						$( '#ttw-settings-max-input-characters-per-day-by-all-users' ).val( 40000 );
						$( '#ttw-settings-max-output-characters-per-day-by-ai' ).val( 40000 );
						$( '#ttw-settings-max-output-tokens-per-day-by-ai' ).val( 50000 );
						$( '#ttw-settings-dialogue-lifetime-max-messages' ).val( 30 );
						break;
					case 3:
						// Medium (~$15 / day)
						$( '#ttw-settings-dialogue-lifetime-minutes' ).val( 1440 );
						$( '#ttw-settings-max-characters-per-user-question' ).val( 300 );
						$( '#ttw-settings-max-words-to-answer' ).val( 150 );
						$( '#ttw-settings-max-input-words-per-day-by-all-users' ).val( 30000 );
						$( '#ttw-settings-max-input-characters-per-day-by-all-users' ).val( 100000 );
						$( '#ttw-settings-max-output-characters-per-day-by-ai' ).val( 100000 );
						$( '#ttw-settings-max-output-tokens-per-day-by-ai' ).val( 100000 );
						$( '#ttw-settings-dialogue-lifetime-max-messages' ).val( 50 );
						break;
					case 4:
						// Large (~$50 / day)
						$( '#ttw-settings-dialogue-lifetime-minutes' ).val( 1440 );
						$( '#ttw-settings-max-characters-per-user-question' ).val( 400 );
						$( '#ttw-settings-max-words-to-answer' ).val( 500 );
						$( '#ttw-settings-max-input-words-per-day-by-all-users' ).val( 150000 );
						$( '#ttw-settings-max-input-characters-per-day-by-all-users' ).val( 500000 );
						$( '#ttw-settings-max-output-characters-per-day-by-ai' ).val( 500000 );
						$( '#ttw-settings-max-output-tokens-per-day-by-ai' ).val( 350000 );
						$( '#ttw-settings-dialogue-lifetime-max-messages' ).val( 100 );
						break;
					case 5:
						// Extra (~$100 / day)
						$( '#ttw-settings-dialogue-lifetime-minutes' ).val( 2880 );
						$( '#ttw-settings-max-characters-per-user-question' ).val( 500 );
						$( '#ttw-settings-max-words-to-answer' ).val( 1000 );
						$( '#ttw-settings-max-input-words-per-day-by-all-users' ).val( 300000 );
						$( '#ttw-settings-max-input-characters-per-day-by-all-users' ).val( 1000000 );
						$( '#ttw-settings-max-output-characters-per-day-by-ai' ).val( 1000000 );
						$( '#ttw-settings-max-output-tokens-per-day-by-ai' ).val( 1000000 );
						$( '#ttw-settings-dialogue-lifetime-max-messages' ).val( 150 );
						break;
					case 6:
						// No limits
						$( '#ttw-settings-dialogue-lifetime-minutes' ).val( 4320 );
						$( '#ttw-settings-max-characters-per-user-question' ).val( 500 );
						$( '#ttw-settings-max-words-to-answer' ).val( 1000 );
						$( '#ttw-settings-max-input-words-per-day-by-all-users' ).val( -1 );
						$( '#ttw-settings-max-input-characters-per-day-by-all-users' ).val( -1 );
						$( '#ttw-settings-max-output-characters-per-day-by-ai' ).val( -1 );
						$( '#ttw-settings-max-output-tokens-per-day-by-ai' ).val( -1 );
						$( '#ttw-settings-dialogue-lifetime-max-messages' ).val( -1 );
						break;
				}
			}
		},

		/**
		 * Reset selected floating bubble appearence button (from simple button to green button)
		 */
		settingsTabResetSelectedBubbleButtonAppearence: function () {
			$( '#ttw-chat-bubbles-types-area' ).find( '[data-role="ttw-bubble-picker-button"]' ).removeClass( 'ttw-green-button' );
			$( '#ttw-chat-bubbles-types-area' ).find( '[data-role="ttw-bubble-picker-button"] .ttw-bubble-select-label' ).show();
			$( '#ttw-chat-bubbles-types-area' ).find( '[data-role="ttw-bubble-picker-button"] .ttw-bubble-selected-label' ).hide();

			let $selectedBubbleInput = $( 'input[name="ttw-chat-bubble-appearence-selector-input"]:checked' );
			if ( $selectedBubbleInput && $selectedBubbleInput.length > 0 ) {
				$selectedBubbleInput.closest( '[data-role="ttw-bubble-picker-button"]' ).addClass( 'ttw-green-button' );
				$selectedBubbleInput.closest( '[data-role="ttw-bubble-picker-button"]' ).find( '.ttw-bubble-select-label' ).hide();
				$selectedBubbleInput.closest( '[data-role="ttw-bubble-picker-button"]' ).find( '.ttw-bubble-selected-label' ).show();
			}
		},

		/**
		 * Reset selected AI character
		 */
		settingsTabResetSelectedAICharacter: function() {
			$( '#ttw-ai-characters-area' ).find( '[data-role="ttw-ai-character-picker-button"]' ).removeClass( 'ttw-green-button' );
			$( '#ttw-ai-characters-area' ).find( '[data-role="ttw-ai-character-picker-button"] .ttw-ai-character-select-label' ).show();
			$( '#ttw-ai-characters-area' ).find( '[data-role="ttw-ai-character-picker-button"] .ttw-ai-character-selected-label' ).hide();

			let $selectedBubbleInput = $( 'input[name="ttw-ai-character-selector-input"]:checked' );
			if ( $selectedBubbleInput && $selectedBubbleInput.length > 0 ) {
				$selectedBubbleInput.closest( '[data-role="ttw-ai-character-picker-button"]' ).addClass( 'ttw-green-button' );
				$selectedBubbleInput.closest( '[data-role="ttw-ai-character-picker-button"]' ).find( '.ttw-ai-character-select-label' ).hide();
				$selectedBubbleInput.closest( '[data-role="ttw-ai-character-picker-button"]' ).find( '.ttw-ai-character-selected-label' ).show();
			}
		},

		/**
		 * Save OpenAI API key
		 */
		settingsTabSaveOpenAIAPIKey: function () {

			// Get OpenAI API key
			let openAIAPIKey = $( '#ttw-settings-chatgpt-api-key' ).val().trim();
			if ( '' === openAIAPIKey ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.setOpenAIKey_ApiKeyIsEmpty );
				return;
			}

			let openAIAPIAddress = $( '#ttw-settings-openai-api-address' ).val();
			if ( '' === openAIAPIAddress ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.setOpenAIKey_ApiAddressIsEmpty );
				return;
			}
			
			// Send data to the server
			talkToWebsiteAdmin.showLoader();
			$.post(
				ajaxurl, 
				{
					action: 'vappv_ttw_save_openai_api_key',
					openai_api_key: openAIAPIKey,
					openai_api_address: openAIAPIAddress,
					nonce: vappv_ttw_admin_js.nonce
				}, 
				function( response ) { 
						
					talkToWebsiteAdmin.hideLoader();
					let saveOpenAIAPIKeyResponse = null; 

					// Decode response
					try {
						saveOpenAIAPIKeyResponse = JSON.parse( response ); 
					}
					catch ( error ) {
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.setOpenAIKey_ServerCallFailed );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_save_openai_api_key()] JSON exception:' );
						console.log( error );
						console.log( '--------------------------------' );
						return;
					}

					// Handle errors
					if ( 'ERROR' === saveOpenAIAPIKeyResponse.AJAXCallResults ) {
						
						// Build error title
						let errorTitle = '';
						if (
							saveOpenAIAPIKeyResponse.TTWErrorCode === 'CHATGPT_API_ERROR' ||
							saveOpenAIAPIKeyResponse.TTWErrorCode === 'OPENAI_API_ERROR'
						) {
							errorTitle = talkToWebsiteAdminLocalization.setOpenAIKey_OpenAIErrorTitle;
						}
						else {
							errorTitle = talkToWebsiteAdminLocalization.setOpenAIKey_OpenAICommonTitle;
						}

						// Build error body
						let errorText = ''; 
						if (
							saveOpenAIAPIKeyResponse.TTWErrorCode === 'CHATGPT_API_ERROR' ||
							saveOpenAIAPIKeyResponse.TTWErrorCode === 'OPENAI_API_ERROR'
						) {
							errorText = saveOpenAIAPIKeyResponse.ErrorDetails.message;
						}
						else {
							errorText = talkToWebsiteAdminLocalization.setOpenAIKey_PleaseContactSupport + '/' + saveOpenAIAPIKeyResponse.ErrorDetails.toString();
						}

						// Double-check for an empty error
						if ( errorTitle === '' ) {
							errorTitle = saveOpenAIAPIKeyResponse.TTWErrorCode;
						}
						if ( errorText === '' ) {
							errorText = talkToWebsiteAdminLocalization.setOpenAIKey_PleaseContactSupport + '/' + saveOpenAIAPIKeyResponse.ErrorDetails.toString();
						}

						talkToWebsiteAdmin.displayError( `<b>${errorTitle}</b>: <br />${errorText}` );
					} 
					else if ( saveOpenAIAPIKeyResponse.AJAXCallResults === 'SUCCESS' && saveOpenAIAPIKeyResponse.Data.Result === 'API_KEY_SAVED' ) {
						// Managed success
						talkToWebsiteAdmin.displayInfo( talkToWebsiteAdminLocalization.setOpenAIKey_ApiKeySaved );
						$( '.ttw-tab-content[data-tabid="settings"]' ).find( '.ttw-buttons-bar' ).show();
						$( '#ttw-settings-tabs-headings ').find( '[data-role="ttw-tab"]' ).show();
						$( '#ttw-firstpage-setup-the-plugin-helping-button' ).hide();
						$( '#ttw-need-help-bottom-area' ).show();
						$( '#ttw-is-chatgpt-key-set' ).val( 'yes' );
						$( '#ttw-settings-tabs-headings' ).find( '[data-tabid="settings-chatgpt-details"]' ).trigger( 'click' );
					}
					else {
						// Something went wrong
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.setOpenAIKey_ServerCallFailed );
						console.log( '[TalkToWebsite, ttw_save_openai_api_key()]: Something went wrong / Unexpected server response.' );
					}
				}
		    ).fail( function( xhr, textStatus, errorThrown ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.setOpenAIKey_ServerCallFailed );
				console.log( '[TalkToWebsite, ttw_save_openai_api_key()]: AJAX call failed.' );
			} );
		},

		/**
		 * Save all plugin settings
		 */
		settingsTabSaveAllPluginSettings: function () {

			// Prepare args
			let savePluginSettingsCallArgs = {
				action: 'vappv_ttw_save_all_settings',
				nonce:                                              vappv_ttw_admin_js.nonce,
				settings_chatgpt_model:                             $( '#ttw-settings-chatgpt-model' ).val(),
				settings_chatgpt_temperature:                       $( '#ttw-settings-chatgpt-temperature' ).val(),
				settings_max_characters_per_user_question:          $( '#ttw-settings-max-characters-per-user-question' ).val(),
				settings_max_words_to_answer:                       $( '#ttw-settings-max-words-to-answer' ).val(),
				settings_website_contacts_page:                     $( '#ttw-settings-website-contacts-page' ).val(),
				settings_any_direct_instructions:                   $( '#ttw-settings-direct-instructions' ).val(),
				settings_primary_language:                          $( '#ttw-settings-primary-language' ).val(),
				settings_humor_level:                               $( '#ttw-settings-humor-level' ).val(),
				settings_try_intercept_leaving_users:               $( '#ttw-settings-try-intercept-leaving-users' ).val(),
				settings_chat_bot_name:                             $( '#ttw-settings-robot-name' ).val(),
				settings_robot_messages_post_scriptum:              $( '#ttw-settings-robot-messages-post-scriptum' ).val(),
				settings_dialogue_lifetime_minutes:                 $( '#ttw-settings-dialogue-lifetime-minutes' ).val(),
				settings_is_chatbot_displayed_right_now:            $( '#ttw-settings-is-chatbot-displayed-right-now' ).val(),
				settings_chat_bubble_image_url:                     $( 'input[name="ttw-chat-bubble-appearence-selector-input"]:checked' ).val(),
				settings_website_contacts_company_email:            $( '#ttw-settings-website-contacts-company-email' ).val(),
				settings_max_input_words_per_day_by_all_users:      $( '#ttw-settings-max-input-words-per-day-by-all-users' ).val(),
				settings_max_input_characters_per_day_by_all_users: $( '#ttw-settings-max-input-characters-per-day-by-all-users' ).val(),
				settings_max_output_characters_per_day_by_ai:       $( '#ttw-settings-max-output-characters-per-day-by-ai' ).val(),
				settings_max_output_tokens_per_day_by_ai:           $( '#ttw-settings-max-output-tokens-per-day-by-ai' ).val(),
				settings_sugar_limits_summary:                      $( '#ttw-settings-sugar-limits-summary' ).val(),
				settings_prohibited_topics_and_words:               $( '#ttw-settings-prohibited-topics-and-words' ).val(),
				settings_show_promo_popup_after_n_seconds:          $( '#ttw-settings-show-promo-popup-after-n-seconds' ).val(),
				settings_show_promo_popup_time_frequency_n_minutes: $( '#ttw-settings-show-promo-popup-time-frequency-n-minutes' ).val(),
				settings_dialogue_lifetime_max_messages:            $( '#ttw-settings-dialogue-lifetime-max-messages' ).val(),
				settings_welcome_message_html:                      $( '#ttw-settings-welcome-message-html' ).val(),
				settings_store_db_on_deletion:                      $( '#ttw-settings-store-db-on-deletion' ).val(),
				settings_show_powered_by_ttw_public_label:          $( '#ttw-settings-show-powered-by-ttw-public-label' ).val(),
				settings_chat_restricted_pages_urlmasks:            $( '#ttw-settings-chat-restricted-pages-urlmasks' ).val(),
				settings_openai_api_address:                        $( '#ttw-settings-openai-api-address' ).val(),
				settings_chat_bubble_ui_position:                   $( '#ttw-settings-chat-bubble-ui-position' ).val(),
				settings_chat_bubble_ui_margin:                     $( '#ttw-settings-chat-bubble-ui-margin' ).val(),
				settings_ai_character:								$( 'input[name="ttw-ai-character-selector-input"]:checked' ).val(),
				settings_website_contacts_company_phone:            '',
				settings_website_contacts_company_address:          '',
				settings_plugin_appearance_custom_css:              '',
			};
 
			// Validate args
			let errorsList = [];

			// Validate ChatGPT model
			if ( talkToWebsiteAdmin.isNullOrEmpty( savePluginSettingsCallArgs.settings_chatgpt_model ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_ChatGPTModelNotSet );
			}
			else if ( $( '#ttw-settings-chatgpt-available-models' ).val().indexOf( savePluginSettingsCallArgs.settings_chatgpt_model ) === -1 ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_WrongCharGPTModel );
			}

			// Validate AI temperature
			if ( talkToWebsiteAdmin.isNullOrEmpty( savePluginSettingsCallArgs.settings_chatgpt_temperature ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_ChatGPTTemperatureNotSet );
			}
			else if ( ! $.isNumeric( savePluginSettingsCallArgs.settings_chatgpt_temperature ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_ChatGPTTemperatureNotNumeric );
			}
			else if ( Number(savePluginSettingsCallArgs.settings_chatgpt_temperature ) < 0 || Number( savePluginSettingsCallArgs.settings_chatgpt_temperature ) > 2 ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_ChatGPTTemperatureWrongVal );
			}

			// Validate dialogue lifetime, minutes
			if ( talkToWebsiteAdmin.isNullOrEmpty( savePluginSettingsCallArgs.settings_dialogue_lifetime_minutes ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_DialogueLifetimeNotSet );
			}
			else if ( ! $.isNumeric( savePluginSettingsCallArgs.settings_dialogue_lifetime_minutes ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_DialogueLifetimeNotNumeric );
			}
			else if ( Number(savePluginSettingsCallArgs.settings_dialogue_lifetime_minutes ) < 0 ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_DialogueLifeTimeNotPositive );
			}

			// Validate max characters count per one user question
			if ( talkToWebsiteAdmin.isNullOrEmpty( savePluginSettingsCallArgs.settings_max_characters_per_user_question ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxCharPerQuestionNotSet );
			}
			else if ( ! $.isNumeric(savePluginSettingsCallArgs.settings_max_characters_per_user_question ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxCharPerQuestionNotNumeric );
			}
			else if ( Number(savePluginSettingsCallArgs.settings_max_characters_per_user_question ) < 1 ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxCharPerQuestionNotPositive );
			}

			// Validate max words to answer
			if ( talkToWebsiteAdmin.isNullOrEmpty( savePluginSettingsCallArgs.settings_max_words_to_answer ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxWordsPerAnswerNotSet );
			}
			else if ( ! $.isNumeric( savePluginSettingsCallArgs.settings_max_words_to_answer ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxWordsPerAnswerNotNumeric );
			}
			else if ( Number( savePluginSettingsCallArgs.settings_max_words_to_answer ) < -1 || Number( savePluginSettingsCallArgs.settings_max_words_to_answer ) === 0 ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxWordsPerAnswerNotPositive );
			}

			// Validate max input words per day
			if ( talkToWebsiteAdmin.isNullOrEmpty( savePluginSettingsCallArgs.settings_max_input_words_per_day_by_all_users ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxInputWordsPerDayNotSet );
			}
			else if ( ! $.isNumeric( savePluginSettingsCallArgs.settings_max_input_words_per_day_by_all_users ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxInputWordsPerDayNotNumeric );
			}
			else if ( Number( savePluginSettingsCallArgs.settings_max_input_words_per_day_by_all_users ) < -1 || Number( savePluginSettingsCallArgs.settings_max_input_words_per_day_by_all_users ) === 0 ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxInputWordsPerDayNotPositive );
			}

			// Validate max input characters per day
			if ( talkToWebsiteAdmin.isNullOrEmpty( savePluginSettingsCallArgs.settings_max_input_characters_per_day_by_all_users ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxInputCharsPerDayNotSet );
			}
			else if ( ! $.isNumeric( savePluginSettingsCallArgs.settings_max_input_characters_per_day_by_all_users ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxInputCharsPerDayNotNumeric );
			}
			else if ( Number( savePluginSettingsCallArgs.settings_max_input_characters_per_day_by_all_users ) < -1 || Number( savePluginSettingsCallArgs.settings_max_input_characters_per_day_by_all_users ) === 0 ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxInputCharsPerDayNotPositive );
			}

			// Validate max output characters per day
			if ( talkToWebsiteAdmin.isNullOrEmpty( savePluginSettingsCallArgs.settings_max_output_characters_per_day_by_ai ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxOutputCharsPerDayNotSet );
			}
			else if ( ! $.isNumeric( savePluginSettingsCallArgs.settings_max_output_characters_per_day_by_ai ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxOutputCharsPerDayNotNumeric );
			}
			else if ( Number( savePluginSettingsCallArgs.settings_max_output_characters_per_day_by_ai ) < -1 || Number( savePluginSettingsCallArgs.settings_max_output_characters_per_day_by_ai ) === 0 ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxOutputCharsPerDayNotPositive );
			}

			// Validate max output tokens per day
			if ( talkToWebsiteAdmin.isNullOrEmpty( savePluginSettingsCallArgs.settings_max_output_tokens_per_day_by_ai ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxOutputTokensPerDayNotSet );
			}
			else if ( ! $.isNumeric( savePluginSettingsCallArgs.settings_max_output_tokens_per_day_by_ai ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxOutputTokensPerDayNotNumeric );
			}
			else if ( Number( savePluginSettingsCallArgs.settings_max_output_tokens_per_day_by_ai ) < -1 || Number( savePluginSettingsCallArgs.settings_max_output_tokens_per_day_by_ai ) === 0 ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_MaxOutputTokensPerDayNotPositive );
			}

			// Validate chat bubble margin
			if ( ! $.isNumeric( savePluginSettingsCallArgs.settings_chat_bubble_ui_margin ) ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_ChatBubbleIndentationWrong );
			}
			else if ( Number( savePluginSettingsCallArgs.settings_chat_bubble_ui_margin ) < -0 ) {
				errorsList.push( talkToWebsiteAdminLocalization.saveSettings_ChatBubbleIndentationWrong );
			}

			if ( errorsList.length > 0 ) {
				let pleaseFixErrorText = talkToWebsiteAdminLocalization.saveSettings_WrongSettingsFixErrors;
				let errorMessageText = `<b>${pleaseFixErrorText}</b>: <br />`;
				errorMessageText += errorsList.join( ';<br />' );
				talkToWebsiteAdmin.displayError( errorMessageText );
				return;
			}

			// Send data to the server
			talkToWebsiteAdmin.showLoader();
			$.post(
				ajaxurl, 
				savePluginSettingsCallArgs, 
				function( response ) {
					
					talkToWebsiteAdmin.hideLoader();
					let savePluginSettingsResponse = null; 

					// Decode response
					try {
						savePluginSettingsResponse = JSON.parse( response );
					}
					catch ( error ) {
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.saveSettings_ServerCallFailed );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_save_all_settings()] JSON exception:' );
						console.log( error );
						console.log( '--------------------------------' );
						return;
					}

					// Handle errors
					if ( savePluginSettingsResponse.AJAXCallResults === 'ERROR' ) {
						// Managed error
						let exceptionOutputText = talkToWebsiteAdminLocalization.saveSettings_ServerCallFailed + ' // ' + savePluginSettingsResponse.TTWErrorCode + '; ' + savePluginSettingsResponse.ErrorDetails;
						talkToWebsiteAdmin.displayError( exceptionOutputText );
						console.log( `[TalkToWebsite, ttw_save_all_settings()] Server side exception; Code = ${savePluginSettingsResponse.TTWErrorCode}; Details = ${savePluginSettingsResponse.ErrorDetails}` );
					}
					else if ( savePluginSettingsResponse.AJAXCallResults === 'SUCCESS' ) {
						// Managed success
						talkToWebsiteAdmin.displayInfo( talkToWebsiteAdminLocalization.saveSettings_Success );
						$( '#ttw-are-plugin-settings-saved' ).val( 'yes' );
						$( '#ttw-tabs-headings' ).find( '[data-role="ttw-tab"][data-tabid="training"]' ).trigger( 'click' );
						$( '[data-role="ttw-settings-not-set"]' ).hide();
						$( '#ttw-training-container, #ttw-logs-container, #traning-tab-bottom-buttons' ).show();
					}
					else {
						// Something went wrong
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.saveSettings_ServerCallFailed );
						console.log( '[TalkToWebsite, ttw_save_all_settings()]: Something went wrong / Unexpected server response.' );
					}

				}
		    ).fail( function( xhr, textStatus, errorThrown ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.saveSettings_ServerCallFailed );
				console.log( '[TalkToWebsite, ttw_save_all_settings()]: AJAX call failed.' );
			} );
		},

		/**
		 * List available AI models (output to the console and save data into DB)
		 */
		settingsTabListAIModels: function () {

			// Show loader and post data to the server
			talkToWebsiteAdmin.showLoader();
			$.post(
				ajaxurl,
				{
					action: 'vappv_ttw_list_available_gpt_models',
					nonce: vappv_ttw_admin_js.nonce
				},
				function( response ) {

					// Process response data
					talkToWebsiteAdmin.hideLoader();
					let availableGPTModelsList = null;

					// Decode response
					try {
						availableGPTModelsList = JSON.parse( response );
					}
					catch ( error ) {
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.listAIModels_ServerCallFailed );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_list_available_gpt_models()] JSON exception:' );
						console.log( error );
						console.log( '--------------------------------' );
						return;
					}

					// Handle errors
					if ( availableGPTModelsList.AJAXCallResults === 'ERROR' ) {
						// Managed error
						let exceptionOutputText = talkToWebsiteAdminLocalization.listAIModels_ServerCallFailed + ' // ' + availableGPTModelsList.TTWErrorCode + '; ' + availableGPTModelsList.ErrorDetails;
						talkToWebsiteAdmin.displayError( exceptionOutputText );
						console.log( `[TalkToWebsite, ttw_list_available_gpt_models()] Server side exception; Code = ${availableGPTModelsList.TTWErrorCode}; Details = ${availableGPTModelsList.ErrorDetails}` );
					}
					else if ( availableGPTModelsList.AJAXCallResults === 'SUCCESS' ) {
						// Managed success
						talkToWebsiteAdmin.displayInfo( talkToWebsiteAdminLocalization.listAIModels_Success );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_list_available_gpt_models()] Available GPT models:' );
						console.log( response );
						console.log( '--------------------------------' );
					}
					else {
						// Something went wrong
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.listAIModels_ServerCallFailed );
						console.log( '[TalkToWebsite, ttw_list_available_gpt_models()]: Something went wrong / Unexpected server response.' );
					}
				}
			).fail( function( xhr, textStatus, errorThrown ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.listAIModels_ServerCallFailed );
				console.log( '[TalkToWebsite, ttw_list_available_gpt_models()]: AJAX call failed.' );
			} );
		},

		/* --------------------------------------------------------------------- */
		/*							  Training tab							 */
		/* --------------------------------------------------------------------- */

		/**
		 * Get training tabs posts list and pages list
		 */
		trainingTabGetAndDrawAllContentFromTheWebsite: function () {

			// Send data to the server
			talkToWebsiteAdmin.showLoader();
			$.post(
				ajaxurl, 
				{
					action: 'vappv_ttw_get_training_items',
					nonce: vappv_ttw_admin_js.nonce
				}, 
				function( response ) {
					
					talkToWebsiteAdmin.hideLoader();
					let websiteMaterials = null;

					// Decode response
					try {
						websiteMaterials = JSON.parse( response ); 
					}
					catch ( error ) {
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.getWebsiteMaterials_ServerCallFailed );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_get_training_items()] JSON exception:' );
						console.log( error );
						console.log( '--------------------------------' );
						return;
					}

					// Handle errors
					if ( websiteMaterials.AJAXCallResults === 'ERROR' ) {
						// Managed error
						let exceptionOutputText = talkToWebsiteAdminLocalization.getWebsiteMaterials_ServerCallFailed + ' // ' + websiteMaterials.TTWErrorCode + '; ' + websiteMaterials.ErrorDetails;
						talkToWebsiteAdmin.displayError( exceptionOutputText );
						console.log( `[TalkToWebsite, ttw_get_training_items()] Server side exception; Code = ${websiteMaterials.TTWErrorCode}; Details = ${websiteMaterials.ErrorDetails}` );
					}
					else if ( websiteMaterials.AJAXCallResults === 'SUCCESS' ) {

						// Managed success
						// Clean old output
						$( '#ttw-training-pages-output, #ttw-training-posts-output' ).empty();

						// Itterate materials
						let materialTypes = Object.keys( websiteMaterials.Data );

						// Set some default icons for items
						let materialTypeIcons = {
							'page': '<div class="wp-menu-image dashicons-before dashicons-admin-page"></div>',
							'post': '<div class="wp-menu-image dashicons-before dashicons-admin-post"></div>'
						};

						// Clean heading tabs
						$( '#ttw-training-tabs-headings, #ttw-training-tabs-content' ).empty();

						// Draw materials (Tabs and according content)
						for ( let t = 0; t < materialTypes.length; t++ ) {

							// Get next material type
							let nextMaterialType = materialTypes[ t ];
							let nextMaterialType_name = nextMaterialType;
							if ( nextMaterialType_name.length > 1 ) {
								nextMaterialType_name = nextMaterialType_name.charAt( 0 ).toUpperCase() + nextMaterialType_name.slice( 1 );
							}

							// Draw according tab (heading)
							let nextTabHeading = `<div class="ttw-tab ttw-training-tab ${( t === 0 ? 'ttw-active' : '' )}" data-role="ttw-tab" data-tabid="${nextMaterialType}">
								<div class="ttw-training-tab-inner-row">`;
							if ( materialTypeIcons[ nextMaterialType ] && typeof materialTypeIcons[ nextMaterialType ] !== 'undefined' ) {
								nextTabHeading += `<div class="icon">${ materialTypeIcons[ nextMaterialType ] }</div>`;
							}
							nextTabHeading += `<div class="text">${nextMaterialType_name}</div>
								</div></div>`;
							$( '#ttw-training-tabs-headings' ).append( nextTabHeading );

							// Draw according tab (inner content)
							let nextTabBody = `<div class="ttw-tab-content ttw-materials-tab ttw-tab-content-first" data-role="ttw-tab-content" data-level="2" data-tabid="${nextMaterialType}" ${(t > 0 ? 'style="display: none;"' : '' )}>
								<div class="ttw-training-list-content" data-role="ttw-training-list-content"></div>
							</div>`;
							$( '#ttw-training-tabs-content' ).append( nextTabBody );

							// Get itterating array link
							let trainingItems = websiteMaterials.Data[ nextMaterialType ];

							// Itterate items
							for ( let q = 0; q < trainingItems.length; q++ ) {

								// Get current item
								let currentItem = trainingItems[ q ];
								if ( currentItem.type !== nextMaterialType ) {
									continue;
								}

								// Build item HTML data
								let trainingItemRowHTML = `<div class="ttw-training-item-row ttw-training-status-${currentItem.training_status}" data-role="training-material" data-material-type="${currentItem.type}" data-material-id="${currentItem.id}">
									<div class="ttw-training-item-info"> 
										<div class="ttw-training-item-material-data ttw-material-naming"> 
											<div class="ttw-material-naming-name"><b>Id:</b> #${currentItem.id}; <b>${talkToWebsiteAdminLocalization.commonText_MaterialName}:</b> <span>${currentItem.name}</span></div> 
										</div>
										<div class="ttw-training-item-material-data ttw-flex-row-std ttw-training-item-primary-info">
											<div class="ttw-material-naming-title">${talkToWebsiteAdminLocalization.commonText_MaterialTitle}: <span>${currentItem.title}</span></div>
										</div>
										<div class="ttw-training-item-material-data ttw-flex-row-std ttw-training-item-primary-info"> 
											<div>${talkToWebsiteAdminLocalization.commonText_Date}: ${currentItem.date}</div>
											<div>${talkToWebsiteAdminLocalization.commonText_Characters}: ${currentItem.content_length_characters};</div>
											<div>${talkToWebsiteAdminLocalization.commonText_Words}: ${currentItem.content_length_words};</div>
											<div>${talkToWebsiteAdminLocalization.commonText_GPTTokens}: ~${currentItem.content_length_tokens};</div>
										</div> 
										<div class="ttw-training-item-material-data ttw-flex-row-std ttw-training-item-status">
											<div><b>${talkToWebsiteAdminLocalization.commonText_Status}:</b> <span class="ttw-material-training-status-label">${currentItem.training_status}</span></div>
											` + ( currentItem.training_status === 'trained' ? `<div>${talkToWebsiteAdminLocalization.getWebsiteMaterials_LastTrained}: ${currentItem.training_last_date}</div>` : '' ) + `
										</div>
									</div>
									<div class="ttw-training-item-buttons ttw-flex-row-std">
										<label data-role="ttw-check-this-material" class="ttw-button ttw-button-mini"><input type="checkbox" data-role="check-material-box" ` + ( currentItem.training_status === 'trained' ? 'checked="checked"' : '' ) + ` /> ${talkToWebsiteAdminLocalization.commonText_Check}</label>
										<a data-role="open-material" class="ttw-button ttw-button-mini" href="${currentItem.url}" target="_blank">${talkToWebsiteAdminLocalization.getWebsiteMaterials_OpenMaterial}</a>
									</div> 
								</div>`;

								// Append item to the page
								$( '#ttw-training-tabs-content' ).find( `[data-role="ttw-tab-content"][data-tabid="${nextMaterialType}"]` ).find( '[data-role="ttw-training-list-content"]' ).append( trainingItemRowHTML );

								// Refresh counters after block loading
								talkToWebsiteAdmin.trainingTabRefreshCheckedMaterialsCounters();
							}
						}

						// Subscribe listed item behavior
						let $trainingItems = $( '#ttw-training-tabs-content' ).find( '[data-role="training-material"]' );

						// Subscribe clicks on "Check / uncheck" material to train
						// Check / Uncheck this material: Add / Remove from training
						$trainingItems.find( '[data-role="ttw-check-this-material"] input' ).off( 'change' ).on( 'change', function( e ) {
							// Subscribe change materials counters
							talkToWebsiteAdmin.trainingTabRefreshCheckedMaterialsCounters();
						} );

						// Subscribe sub-tabs clicks into training tab
						$( '#ttw-training-tabs-headings' ).find( '[data-role="ttw-tab"]' ).off( 'click' ).on( 'click', function( e ) {
							let tabId = $( this ).attr( 'data-tabid' );

							$( '#ttw-training-tabs-headings' ).find( '[data-role="ttw-tab"]' ).removeClass( 'ttw-active' );
							$( this ).addClass( 'ttw-active' );

							$( '#ttw-training-tabs-content' ).find( '[data-role="ttw-tab-content"][data-level="2"]' ).hide();
							$( '#ttw-training-tabs-content' ).find( `[data-role="ttw-tab-content"][data-level="2"][data-tabid="${tabId}"]` ).show();
						} );

					}
					else {
						// Something went wrong
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.getWebsiteMaterials_ServerCallFailed );
						console.log( '[TalkToWebsite, ttw_get_training_items()]: Something went wrong / Unexpected server response.' );
					}
				}
			).fail( function( xhr, textStatus, errorThrown ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.getWebsiteMaterials_ServerCallFailed );
				console.log( '[TalkToWebsite, ttw_get_training_items()]: AJAX call failed.' );
			} );
		},

		/**
		 * Make AI training based on specified materials (pages / posts / etc.)
		 */
		trainingTabMakeTraining: function () {

			// Collect items to train
			let itemsToTrain = [];
			let $allItemsIntoTrainingTab = $( '#ttw-training-website-content' ).find( '[data-role="training-material"]' );
			for ( let q = 0; q < $allItemsIntoTrainingTab.length; q++ ) {
				let $next_item_row = $( $allItemsIntoTrainingTab[ q ] );
				if ( $next_item_row.find( '[data-role="check-material-box"]' ).is( ':checked' ) ) {
					let materialType = $next_item_row.attr( 'data-material-type' );
					let materialId = $next_item_row.attr( 'data-material-id' );
					itemsToTrain.push( {
						material_id: materialId,
						material_type: materialType
					} );
				}
			}

			// Check for empty training set
			if ( itemsToTrain.length === 0 ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.trainAI_NoMaterialsSelected );
				return;
			}

			// Show loader and post data to the server
			talkToWebsiteAdmin.showLoader();
			let itemsToTrainJSONText = JSON.stringify( itemsToTrain );
			$.post(
				ajaxurl, 
				{
					action: 'vappv_ttw_train_ai',
					materials_to_train: itemsToTrainJSONText,
					nonce: vappv_ttw_admin_js.nonce
				}, 
				function( response ) {
					
					// Process response data
					// console.log( response );
					talkToWebsiteAdmin.hideLoader(); 
					let aiTrainedResponse = null;

					// Decode response
					try {
						aiTrainedResponse = JSON.parse( response );
					}
					catch ( error ) {
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.trainAI_ServerCallFailed );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_train_ai()] JSON exception:' );
						console.log( error );
						console.log( '--------------------------------' );
						return;
					}

					// Handle errors
					if ( aiTrainedResponse.AJAXCallResults === 'ERROR' ) {
						// Managed error
						let exceptionOutputText = talkToWebsiteAdminLocalization.trainAI_ServerCallFailed + ' // ' + aiTrainedResponse.TTWErrorCode + '; ' + aiTrainedResponse.ErrorDetails;
						talkToWebsiteAdmin.displayError( exceptionOutputText );
						console.log( `[TalkToWebsite, ttw_train_ai()] Server side exception; Code = ${aiTrainedResponse.TTWErrorCode}; Details = ${aiTrainedResponse.ErrorDetails}` );
					}
					else if ( aiTrainedResponse.AJAXCallResults === 'SUCCESS' ) {
						// Managed success
						talkToWebsiteAdmin.displayInfo( talkToWebsiteAdminLocalization.trainAI_Success, function() {
							$( '#ttw-dialogue-input-textarea' ).select().focus();
						} );
						$( '#ttw-is-assistant-assigned' ).val( 'yes' );
						$( '#ttw-testing-container' ).show();
						$( '#ttw-tabs-headings' ).find( '[data-role="ttw-tab"][data-tabid="testing"]' ).trigger( 'click' );
						$( '#ttw-trained-assistant-tab-heading-data' ).empty().html( `<span>${talkToWebsiteAdminLocalization.trainAI_ReloadPageToViewStats}</span>` );
						$( '[data-role="ttw-ai-training-not-completed"]' ).hide();
						$( '#ttw-chatbot-enabled-plugin-top-notification' ).show();
						$( '#ttw-plugin-current-limits' ).show();
						$( '#ttw-config-and-train-plugin-to-start' ).hide();
					}
					else {
						// Something went wrong
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.trainAI_ServerCallFailed );
						console.log( '[TalkToWebsite, ttw_train_ai()]: Something went wrong / Unexpected server response.' );
					}
				}
		    ).fail( function( xhr, textStatus, errorThrown ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.trainAI_ServerCallFailed );
				console.log( '[TalkToWebsite, ttw_train_ai()]: AJAX call failed.' );
				talkToWebsiteAdmin.hideLoader();
			} );
		},

		/**
		 * Refresh checked materials counters
		 */
		trainingTabRefreshCheckedMaterialsCounters: function () {

			let $trainingBlock = $( '#ttw-training-website-content' );
			let $trainigBlockTabs = $trainingBlock.find( '#ttw-training-tabs-headings [data-role="ttw-tab"]' );
			let totalCheckedItemsCount = 0;
			let totalItemsCount = 0;

			for ( let t = 0; t < $trainigBlockTabs.length; t++ ) {

				// Prepare params
				let $tabBlock = $trainigBlockTabs.eq( t );
				let $tabInnerRow = $tabBlock.find( '.ttw-training-tab-inner-row' ).eq( 0 );
				let tabType = $tabBlock.attr( 'data-tabid' );
				let tabContentBlock = $( '#ttw-training-tabs-content' ).find( `[data-role="ttw-tab-content"][data-tabid="${tabType}"]` );
				let tabItemsCount = $( tabContentBlock ).find( '[data-role="training-material"]' ).length;
				let $tabContentCheckboxes = $( tabContentBlock ).find( 'input[type="checkbox"][data-role="check-material-box"]:checked' );
				totalCheckedItemsCount += $tabContentCheckboxes.length;
				totalItemsCount += tabItemsCount;
			
				// Update tab counter
				$tabInnerRow.find( '.ttw-training-tab-checked-items-counter' ).remove();
				$tabInnerRow.append( `<div class="ttw-training-tab-checked-items-counter">(x${$tabContentCheckboxes.length}/${tabItemsCount})</div>` );
			}

			$( '#ttw-make-training' ).find( '.ttw-training-tab-checked-items-counter' ).remove();
			$( '#ttw-make-training' ).append( `<div class="ttw-training-tab-checked-items-counter">(x${totalCheckedItemsCount}/${totalItemsCount})</div>` );

		},

		/* --------------------------------------------------------------------- */
		/*					Testing / Admin dialogue tab					   */
		/* --------------------------------------------------------------------- */

		/**
		 * Lock chat input field and prevent messaging
		 */
		lockChatMessageInputArea: function() {
			talkToWebsiteAdmin.sendChatMessageMutex = true;
			$( '#ttw-dialogue-input-textarea' ).val( '' ).prop( 'disabled', true );
			$( '#ttw-dialogue-input-textarea, #ttw-dialogue-send-message-button' ).addClass( 'ttw-disabled' );
			setTimeout( function() {
				$( '#ttw-dialogue-input-textarea' ).val( '' );
			}, 150);
		},

		/**
		 * Unlock chat input field and allow messaging
		 */
		unlockChatMessageInputArea: function() {
			talkToWebsiteAdmin.sendChatMessageMutex = false;
			$( '#ttw-dialogue-input-textarea, #ttw-dialogue-send-message-button' ).removeClass( 'ttw-disabled' );
			$( '#ttw-dialogue-input-textarea' ).prop( 'disabled', false ).select().focus();
		},

		/**
		 * Get admin dialogue messages list
		 * @param {string} dataDirection { 'backward' || 'forward' }
		 * @param {number} lastKnownID Last known message identifier
		 */
		adminDialogueTabGetAndDrawAllAdminMessages: function( dataDirection, lastKnownID ) {

			if ( talkToWebsiteAdmin.adminChatMessagesLoadingMutex === true ) {
				return;
			}
			talkToWebsiteAdmin.adminChatMessagesLoadingMutex = true;

			// Prepare params
			let limit = 25;
			if ( typeof dataDirection === 'undefined' || dataDirection === null || dataDirection === '' ) {
				dataDirection = 'backward';
			}
			if ( typeof lastKnownID === 'undefined' || lastKnownID === null ) {
				lastKnownID = null;
			}

			// Draw waiting loader
			if ( lastKnownID === null || dataDirection === 'forward' ) {
				talkToWebsiteAdmin.adminDialogueTabRenderLoadingMessages();
			}
			else if ( dataDirection === 'backward' ) {
				talkToWebsiteAdmin.adminDialogueTabRenderLoadingMessages( 'before' );
			}

			$.post(
				ajaxurl, 
				{
					action: 'vappv_ttw_list_admin_messages',
					data_direction: dataDirection,
					last_known_id: lastKnownID,
					data_limit: limit,
					nonce: vappv_ttw_admin_js.nonce
				}, 
				function( response ) {
							
					// console.log( response );
					let getAdminMessagesResponse = null;
					talkToWebsiteAdmin.adminChatMessagesLoadingMutex = false;

					// Decode response
					try {
						getAdminMessagesResponse = JSON.parse( response ); 
					}
					catch ( error ) {
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.listAdminMessages_ServerCallFailed );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_list_admin_messages()] JSON exception:' );
						console.log( error );
						console.log( '--------------------------------' );
						return;
					}

					// Remove loader
					$( '#ttw-loading-admin-messages-text-block' ).remove();

					// Handle errors
					if ( getAdminMessagesResponse.AJAXCallResults === 'ERROR' ) {
						// Managed error
						let exceptionOutputText = talkToWebsiteAdminLocalization.listAdminMessages_ServerCallFailed + ' // ' + getAdminMessagesResponse.TTWErrorCode + '; ' + getAdminMessagesResponse.ErrorDetails;
						talkToWebsiteAdmin.displayError( exceptionOutputText );
						console.log( `[TalkToWebsite, ttw_list_admin_messages()] Server side exception; Code = ${getAdminMessagesResponse.TTWErrorCode}; Details = ${getAdminMessagesResponse.ErrorDetails}` );
					}
					else if ( getAdminMessagesResponse.AJAXCallResults === 'SUCCESS' ) {
						
						// Managed success: itterate messages and update data cache
						for ( let q = 0; q < getAdminMessagesResponse.Data.Messages.length; q++ ) {
							
							if ( getAdminMessagesResponse.Data.Messages[ q ].message_date.indexOf( 'Z' ) === -1 ) {
								getAdminMessagesResponse.Data.Messages[ q ].message_date += 'Z';
							}

							// Append message to the cache
							if ( dataDirection === 'forward' ) {
								talkToWebsiteAdmin.adminMessagesCache.push( getAdminMessagesResponse.Data.Messages[ q ] );
								talkToWebsiteAdmin.adminDialogueTabRenderChatMessage( getAdminMessagesResponse.Data.Messages[ q ], 'append', true );
							}
							else if ( dataDirection === 'backward' ) {
								talkToWebsiteAdmin.adminMessagesCache = talkToWebsiteAdmin.arrayPrepend( getAdminMessagesResponse.Data.Messages[ q ], talkToWebsiteAdmin.adminMessagesCache );
								talkToWebsiteAdmin.adminDialogueTabRenderChatMessage( getAdminMessagesResponse.Data.Messages[ q ], 'prepend', false );
							}
						}

						// Check for pending messages
						if ( talkToWebsiteAdmin.adminMessagesCache !== null && talkToWebsiteAdmin.adminMessagesCache.length > 0 ) {
							let last_message = talkToWebsiteAdmin.adminMessagesCache[ talkToWebsiteAdmin.adminMessagesCache.length - 1 ];
							if ( 
								last_message.sender_role === 'user' && 
								( last_message.is_responded_by_ai === null || last_message.is_responded_by_ai === 0 || last_message.is_responded_by_ai === '0' ) &&
								( last_message.is_executed_with_error === null || last_message.is_executed_with_error === '' )
							) {
								talkToWebsiteAdmin.adminDialogueTabTryFetchPendingAIResponses();
							}
						}
						
						// Draw dialogues separators
						talkToWebsiteAdmin.adminDialogueTabDrawDialoguesSeparators();

						// Scroll page to the bottom if initial loading occured
						if ( lastKnownID === null || dataDirection === 'forward' ) {
							$( '#ttw-dialogue-block' ).scrollTop( $( '#ttw-dialogue-block' ).prop( 'scrollHeight' ) );
							setTimeout( function() {
								$( '#ttw-dialogue-block' ).scrollTop( $( '#ttw-dialogue-block' ).prop( 'scrollHeight' ) );
							}, 200 );
						}

						// Check movement to the backward direction
						if ( dataDirection === 'backward' ) {
							/* Just leave the scroll in current position (revert scroll before new items prepend) */
							if ( lastKnownID ) {
								$( '#ttw-dialogue-block' ).scrollTop( 1 );
								let $lastKnownMessageObject = $( '#ttw-dialogue-block' ).find( `[data-role='ttw-chat-message'][data-id='${lastKnownID}']` );
								$lastKnownMessageObject.get( 0 ).scrollIntoView();
							}
							else {
								$( '#ttw-dialogue-block' ).scrollTop( 1 );
							}

							// Check are more messages during scrolling to the top
							if ( getAdminMessagesResponse.Data.AreMoreMessages === false || getAdminMessagesResponse.Data.AreMoreMessages === 'false' ) {
								talkToWebsiteAdmin.areMoreMessagesInBackwardDirection = false;

								// Check for initial data loading (no messages at all)
								if ( talkToWebsiteAdmin.adminMessagesCache.length === 0 ) {
									talkToWebsiteAdmin.renderWelcomeToTheChatMessage();
								}
							}
						}
					}
					else {
						// Something went wrong
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.listAdminMessages_ServerCallFailed );
						console.log( '[TalkToWebsite, ttw_list_admin_messages()]: Something went wrong / Unexpected server response.' );
					}
				}
			).fail( function( xhr, textStatus, errorThrown ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.listAdminMessages_ServerCallFailed );
				console.log( '[TalkToWebsite, ttw_list_admin_messages()]: AJAX call failed.' );
				talkToWebsiteAdmin.adminChatMessagesLoadingMutex = false;
			} );
		},

		/**
		 * Send chat message to the admin dialogue
		 */
		adminDialogueTabSendChatMessage: function () {

			// Check mutex
			if ( talkToWebsiteAdmin.sendChatMessageMutex === true ) {
				return;
			}

			// Get and validate chat message
			let messageText = $( '#ttw-dialogue-input-textarea' ).val().trim();

			// Make some preparations to message
			// Remove all unnecessary tags
			messageText = messageText.replaceAll( "`", "'" );
			messageText = messageText.replaceAll( ">", "&gt;" );
			messageText = messageText.replaceAll( "<", "&lt;" );
			messageText = $( '<p id="ttw-message-temp-buffer-notags">' + messageText + '</p>' ).text();
			$( '#ttw-message-temp-buffer-notags' ).remove();

			// Check message for content
			if ( typeof messageText === 'undefined' || messageText === null || messageText.trim().length < 1 ) {
				alert( talkToWebsiteAdminLocalization.sendChatMessageAdmin_EmptyMessage );
				$( '#ttw-dialogue-input-textarea' ).val( '' ).select().focus();
				talkToWebsiteAdmin.unlockChatMessageInputArea();
				return;
			}

			// Drop message to the chat window, then draw loader
			let messageGUID = talkToWebsiteAdmin.generateUUIDv4();
			talkToWebsiteAdmin.adminDialogueTabRenderChatMessage( {
				id: -1,
				message_guid: messageGUID,
				message_text: messageText,
				dialogueId: -1,
				message_date: new Date().toString(),
				sender_role: 'user',
			}, 'append', true );

			// Render "Waiting for AI response"
			talkToWebsiteAdmin.adminDialogueTabRenderWaitingForAIResponse();

			// Lock input field
			talkToWebsiteAdmin.lockChatMessageInputArea();

			// Send message to the backend
			$.post(
				ajaxurl,
				{
					action: 'vappv_ttw_send_message_as_admin',
					message_guid: messageGUID,
					message_text: messageText,
					nonce: vappv_ttw_admin_js.nonce
				},
				function( response ) {

					//console.log(response);
					let processedAdminMessageData = null;

					// Decode response
					try {
						processedAdminMessageData = JSON.parse( response );
					}
					catch ( error ) {
						talkToWebsiteAdmin.unlockChatMessageInputArea();
						$( '#ttw-waiting-for-ai-response-text-block' ).remove();
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.sendChatMessageAdmin_ServerCallFailed );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_send_message_as_admin()] JSON exception:' );
						console.log( error );
						console.log( '--------------------------------' );
						return;
					}

					// Handle errors
					if ( processedAdminMessageData.AJAXCallResults === 'ERROR' ) {
						// Managed error
						talkToWebsiteAdmin.unlockChatMessageInputArea();
						$( '#ttw-waiting-for-ai-response-text-block' ).remove();

						let exceptionOutputText = talkToWebsiteAdminLocalization.sendChatMessageAdmin_ServerCallFailed + ' // ' + processedAdminMessageData.TTWErrorCode + '; ' + processedAdminMessageData.ErrorDetails;
						talkToWebsiteAdmin.displayError( exceptionOutputText );
						console.log( `[TalkToWebsite, ttw_send_message_as_admin()] Server side exception; Code = ${processedAdminMessageData.TTWErrorCode}; Details = ${processedAdminMessageData.ErrorDetails}` );
					}
					else if ( processedAdminMessageData.AJAXCallResults === 'SUCCESS' ) {
						// Managed success
						// Try fetch response from AI
						talkToWebsiteAdmin.adminDialogueTabTryFetchPendingAIResponses();
						 
						// Complete message params
						let insertedMessageObject = processedAdminMessageData.Data.InsertedMessage;
						let $chatMessageBlock = $( '#ttw-dialogue-block' ).find( `[data-role="ttw-chat-message"][data-messageguid="${messageGUID}"]` );
						$chatMessageBlock
							.attr( 'data-dialogue-id', insertedMessageObject.dialogueId )
							.attr( 'data-id', insertedMessageObject.id );

						// Draw dialogues separators
						talkToWebsiteAdmin.adminDialogueTabDrawDialoguesSeparators();
						$( '#ttw-dialogue-block' ).scrollTop( $( '#ttw-dialogue-block' ).prop( 'scrollHeight' ) );

						// Add message to the cache
						talkToWebsiteAdmin.adminMessagesCache.push( processedAdminMessageData.Data.InsertedMessage );
					}
					else {
						// Something went wrong
						talkToWebsiteAdmin.unlockChatMessageInputArea();
						$( '#ttw-waiting-for-ai-response-text-block' ).remove();

						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.sendChatMessageAdmin_ServerCallFailed );
						console.log( '[TalkToWebsite, ttw_send_message_as_admin()]: Something went wrong / Unexpected server response.' );
					}
				}
			).fail( function( xhr, textStatus, errorThrown ) {
				talkToWebsiteAdmin.unlockChatMessageInputArea();
				$( '#ttw-waiting-for-ai-response-text-block' ).remove();

				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.sendChatMessageAdmin_ServerCallFailed );
				console.log( '[TalkToWebsite, ttw_send_message_as_admin()]: AJAX call failed.' );
			} );
		},

		/**
		 * Try to fetch pending AI responses
		 */
		adminDialogueTabTryFetchPendingAIResponses: function () {
			$.post(
				ajaxurl, 
				{
					action: 'vappv_ttw_try_get_message_responses_from_ai_as_admin',
					nonce: vappv_ttw_admin_js.nonce
				}, 
				function( response ){
						  
					let responseFromAI = null;

					// Decode response
					try {
						responseFromAI = JSON.parse( response );
					}
					catch ( error ) {
						talkToWebsiteAdmin.unlockChatMessageInputArea();
						$( '#ttw-waiting-for-ai-response-text-block' ).remove();
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.tryGetAIResponse_ServerCallFailed );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_try_get_message_responses_from_ai_as_admin()] JSON exception:' );
						console.log( error );
						console.log( '--------------------------------' );
						return;
					}

					// Handle errors
					if ( responseFromAI.AJAXCallResults === 'ERROR' ) {
						// Managed error
						talkToWebsiteAdmin.unlockChatMessageInputArea();
						$( '#ttw-waiting-for-ai-response-text-block' ).remove();

						let exceptionOutputText = talkToWebsiteAdminLocalization.tryGetAIResponse_ServerCallFailed + ' // ' + responseFromAI.TTWErrorCode + '; ' + responseFromAI.ErrorDetails;
						talkToWebsiteAdmin.displayError( exceptionOutputText );
						console.log( `[TalkToWebsite, ttw_try_get_message_responses_from_ai_as_admin()] Server side exception; Code = ${responseFromAI.TTWErrorCode}; Details = ${responseFromAI.ErrorDetails}` );
					}
					else if ( responseFromAI.AJAXCallResults === 'SUCCESS' ) {
						  
						// Handle results
						if ( responseFromAI.Data.ExtractionResults === 'no_new_messages' ) {
							// There are no pending results from AI
							talkToWebsiteAdmin.unlockChatMessageInputArea();
							$( '#ttw-waiting-for-ai-response-text-block' ).remove();
							return;
						}
						if ( responseFromAI.Data.ExtractionResults === 'last_message_executed_with_error' ) {
							// Last message executed with error
							talkToWebsiteAdmin.unlockChatMessageInputArea();
							$( '#ttw-waiting-for-ai-response-text-block' ).remove();
							return;
						}
						if ( responseFromAI.Data.ExtractionResults === 'ai_answer_is_still_pending' ) {
							// Last message executed with error
							setTimeout(function () {
								talkToWebsiteAdmin.adminDialogueTabTryFetchPendingAIResponses();
							}, 1500 );
							return;
						}

						// -------------------------------------------------------------------------------------
						// Managed success
						for ( let q = 0; q < responseFromAI.Data.Messages.length; q++ ) {
							if ( responseFromAI.Data.Messages[ q ].message_date.indexOf( 'Z' ) === -1 ) {
								responseFromAI.Data.Messages[ q ].message_date += 'Z';
							}
							talkToWebsiteAdmin.adminMessagesCache.push( responseFromAI.Data.Messages[ q ] );
							talkToWebsiteAdmin.adminDialogueTabRenderChatMessage( responseFromAI.Data.Messages[ q ], 'append', true );
						}
						talkToWebsiteAdmin.unlockChatMessageInputArea();
						$( '#ttw-waiting-for-ai-response-text-block' ).remove();
						// -------------------------------------------------------------------------------------
					}
					else {
						// Something went wrong
						talkToWebsiteAdmin.unlockChatMessageInputArea();
						$( '#ttw-waiting-for-ai-response-text-block' ).remove();
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.tryGetAIResponse_ServerCallFailed );
						console.log( '[TalkToWebsite, ttw_try_get_message_responses_from_ai_as_admin()]: Something went wrong / Unexpected server response.' );
					}
				}
		    ).fail( function( xhr, textStatus, errorThrown ) {
				talkToWebsiteAdmin.unlockChatMessageInputArea();
				$( '#ttw-waiting-for-ai-response-text-block' ).remove();
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.tryGetAIResponse_ServerCallFailed );
				console.log( '[TalkToWebsite, ttw_try_get_message_responses_from_ai_as_admin()]: AJAX call failed.' );
			} );
		},

		/**
		 * Get chat message HTML code
		 * @param {Object} messageObject
		 * @returns {string}
		 */
		getChatMessageHTML: function( messageObject ) {
			
			// ---------------------------------------
			// Prepare message parts
			// Prepare post scriptum block
			let messagePostScriptum = talkToWebsiteAdmin.prepareMessageText( $( '#ttw-settings-robot-messages-post-scriptum' ).val() );
			let messagePostScriptumHTML = '';
			if (
				messageObject.sender_role === 'ai' &&
				messagePostScriptum &&
				messagePostScriptum.length > 0
			) {
				messagePostScriptumHTML = `<div class="ttw-message-body-ps">P.S. ${messagePostScriptum}</div>`;
			}

			// Prepare 'Is message URL(s) followed by user'
			let messageIsURLFollowedByUserHTML = '';
			if (
				messageObject.sender_role === 'ai' &&
				messageObject.is_url_followed_by_sender &&
				Number( messageObject.is_url_followed_by_sender ) === 1
			) {
				messageIsURLFollowedByUserHTML = `<div class="ttw-urls-into-message-are-followed-by-user"><span class="dashicons dashicons-admin-links"></span> ${talkToWebsiteAdminLocalization.renderChatMessage_UserClickedOnTheLink}</div>`;
			}
			
			// Prepare username block
			let messageSenderNameAndIcon = '';
			if ( messageObject.sender_role === 'user' ) {
				// Message sent by user
				let userName = 'Admin';
				if ( messageObject.user_name && messageObject.user_name.length > 0 ) {
					userName = messageObject.user_name;
				}
				messageSenderNameAndIcon = `<span>${userName}</span> <span class="ttw-chat-user-icon"><svg width="20px" height="20px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="24" /><path fill-rule="evenodd" clip-rule="evenodd" d="M6 8C6 4.68629 8.68629 2 12 2C15.3137 2 18 4.68629 18 8C18 11.3137 15.3137 14 12 14C8.68629 14 6 11.3137 6 8Z" fill="#323232"/><path fill-rule="evenodd" clip-rule="evenodd" d="M5.43094 16.9025C7.05587 16.2213 9.2233 16 12 16C14.771 16 16.9351 16.2204 18.5586 16.8981C20.3012 17.6255 21.3708 18.8613 21.941 20.6587C22.1528 21.3267 21.6518 22 20.9592 22H3.03459C2.34482 22 1.84679 21.3297 2.0569 20.6654C2.62537 18.8681 3.69119 17.6318 5.43094 16.9025Z" fill="#323232"/></svg></span>`;
			}
			else {
				// Message sent by AI
				let aiName = $( '#ttw-settings-robot-name' ).val();
				messageSenderNameAndIcon = `<span class="ttw-chat-ai-icon"><svg width="20px" height="20px" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><title>ai</title><g id="Layer_2" data-name="Layer 2"><g id="invisible_box" data-name="invisible box"><rect width="48" height="48" fill="none"></rect></g><g id="Q3_icons" data-name="Q3 icons"><g><path d="M45.6,18.7,41,14.9V7.5a1,1,0,0,0-.6-.9L30.5,2.1h-.4l-.6.2L24,5.9,18.5,2.2,17.9,2h-.4L7.6,6.6a1,1,0,0,0-.6.9v7.4L2.4,18.7a.8.8,0,0,0-.4.8v9H2a.8.8,0,0,0,.4.8L7,33.1v7.4a1,1,0,0,0,.6.9l9.9,4.5h.4l.6-.2L24,42.1l5.5,3.7.6.2h.4l9.9-4.5a1,1,0,0,0,.6-.9V33.1l4.6-3.8a.8.8,0,0,0,.4-.7V19.4h0A.8.8,0,0,0,45.6,18.7Zm-5.1,6.8H42v1.6l-3.5,2.8-.4.3-.4-.2a1.4,1.4,0,0,0-2,.7,1.5,1.5,0,0,0,.6,2l.7.3h0v5.4l-6.6,3.1-4.2-2.8-.7-.5V25.5H27a1.5,1.5,0,0,0,0-3H25.5V9.7l.7-.5,4.2-2.8L37,9.5v5.4h0l-.7.3a1.5,1.5,0,0,0-.6,2,1.4,1.4,0,0,0,1.3.9l.7-.2.4-.2.4.3L42,20.9v1.6H40.5a1.5,1.5,0,0,0,0,3ZM21,25.5h1.5V38.3l-.7.5-4.2,2.8L11,38.5V33.1h0l.7-.3a1.5,1.5,0,0,0,.6-2,1.4,1.4,0,0,0-2-.7l-.4.2-.4-.3L6,27.1V25.5H7.5a1.5,1.5,0,0,0,0-3H6V20.9l3.5-2.8.4-.3.4.2.7.2a1.4,1.4,0,0,0,1.3-.9,1.5,1.5,0,0,0-.6-2L11,15h0V9.5l6.6-3.1,4.2,2.8.7.5V22.5H21a1.5,1.5,0,0,0,0,3Z"></path><path d="M13.9,9.9a1.8,1.8,0,0,0,0,2.2l2.6,2.5v2.8l-4,4v5.2l4,4v2.8l-2.6,2.5a1.8,1.8,0,0,0,0,2.2,1.5,1.5,0,0,0,1.1.4,1.5,1.5,0,0,0,1.1-.4l3.4-3.5V29.4l-4-4V22.6l4-4V13.4L16.1,9.9A1.8,1.8,0,0,0,13.9,9.9Z"></path><path d="M31.5,14.6l2.6-2.5a1.8,1.8,0,0,0,0-2.2,1.8,1.8,0,0,0-2.2,0l-3.4,3.5v5.2l4,4v2.8l-4,4v5.2l3.4,3.5a1.7,1.7,0,0,0,2.2,0,1.8,1.8,0,0,0,0-2.2l-2.6-2.5V30.6l4-4V21.4l-4-4Z"></path></g></g></g></svg></span> <span>${aiName}</span>`;
			}
			// ---------------------------------------

			// Prepare message HTML
			let messageHTML = `<div class="ttw-chat-message ttw-chat-message-by-${messageObject.sender_role}" data-messageguid="${messageObject.message_guid}" data-id="${messageObject.id}" data-role="ttw-chat-message" data-dialogue-id="${messageObject.dialogueId}">
				<div class="ttw-message-body">
					<div class="ttw-message-body-text">${talkToWebsiteAdmin.prepareMessageText( messageObject.message_text )}</div>
					${messagePostScriptumHTML}
					${messageIsURLFollowedByUserHTML}
				</div>
				<div class="ttw-message-triangle-container">
					<div class="ttw-message-triangle-cloud-background-shadow"></div>
					<div class="ttw-message-triangle-cloud-arrow"></div>
				</div>
				<div class="ttw-message-meta">
					<div class="ttw-message-meta-username">${messageSenderNameAndIcon}</div>
					<div class="ttw-message-meta-sentdate">${talkToWebsiteAdmin.formatDateYYYYMMDD_comma_HHMM( new Date( messageObject.message_date ) )}</div>
				</div>
			</div>`;

			return messageHTML;
		},

		/**
		 * Render new chat message
		 * @param {Object} message_object
		 * @param {string} drawType
		 * @param {boolean} autoScroll
		 */
		adminDialogueTabRenderChatMessage: function( message_object, drawType, autoScroll ) {
			
			// Get chat message HTML code
			let messageHTML = talkToWebsiteAdmin.getChatMessageHTML( message_object );

			// Append / Prepend message to the chat
			if ( ! drawType || drawType === 'append' ) {
				$( '#ttw-dialogue-block' ).append( messageHTML );
				if ( autoScroll === true ) {
					$( '#ttw-dialogue-block' ).scrollTop( $( '#ttw-dialogue-block' ).prop( 'scrollHeight' ) );
				}
			}
			else if ( drawType === 'prepend' ) {
				$( '#ttw-dialogue-block' ).prepend( messageHTML );
				if ( autoScroll === true ) {
					$( '#ttw-dialogue-block' ).scrollTop( 0 );
				}
			}
		},

		/**
		 * Render 'Waiting for AI response' block
		 */
		adminDialogueTabRenderWaitingForAIResponse: function () {
			let waitingForAIResponseHTML = `<div class="ttw-waiting-for-ai-response-text-block" id="ttw-waiting-for-ai-response-text-block" data-role="ttw-waiting-for-ai-response-text-block">
				<div class="ttw-waiting-for-ai-response-circle-loader">
					<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="25px" height="25px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><g transform="translate(80,50)"><g transform="rotate(0)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="1"><animateTransform attributeName="transform" type="scale" begin="-0.875s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.875s"></animate></circle></g></g><g transform="translate(71.21320343559643,71.21320343559643)"><g transform="rotate(45)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.875"><animateTransform attributeName="transform" type="scale" begin="-0.75s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.75s"></animate></circle></g></g><g transform="translate(50,80)"><g transform="rotate(90)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.75"><animateTransform attributeName="transform" type="scale" begin="-0.625s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.625s"></animate></circle></g></g><g transform="translate(28.786796564403577,71.21320343559643)"><g transform="rotate(135)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.625"><animateTransform attributeName="transform" type="scale" begin="-0.5s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.5s"></animate></circle></g></g><g transform="translate(20,50.00000000000001)"><g transform="rotate(180)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.5"><animateTransform attributeName="transform" type="scale" begin="-0.375s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.375s"></animate></circle></g></g><g transform="translate(28.78679656440357,28.786796564403577)"><g transform="rotate(225)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.375"><animateTransform attributeName="transform" type="scale" begin="-0.25s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.25s"></animate></circle></g></g><g transform="translate(49.99999999999999,20)"><g transform="rotate(270)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.25"><animateTransform attributeName="transform" type="scale" begin="-0.125s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.125s"></animate></circle></g></g><g transform="translate(71.21320343559643,28.78679656440357)"><g transform="rotate(315)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.125"><animateTransform attributeName="transform" type="scale" begin="0s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="0s"></animate></circle></g></g><!-- [ldio] generated by https://loading.io/ --></svg>
				</div>
				<div class="ttw-waiting-for-ai-response-label-text">
					${talkToWebsiteAdminLocalization.html_waitingForAIResponse}
				</div>
			</div>`;
			$( '#ttw-dialogue-block' ).append( waitingForAIResponseHTML ).scrollTop( $( '#ttw-dialogue-block' ).prop( 'scrollHeight' ) );
		},

		/**
		 * Render 'Loading messages' block
		 * @param {string} position Render loader message before / after chat messages
		 */
		adminDialogueTabRenderLoadingMessages: function( position ) {
			let loadingMessagesHTML = `<div class="ttw-loading-admin-messages-text-block ${position === 'before' ? 'ttw-loader-before-chat' : ''}" id="ttw-loading-admin-messages-text-block" data-role="ttw-loading-admin-messages-text-block">
				<div class="ttw-loading-admin-messages-circle-loader">
					<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="25px" height="25px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><g transform="translate(80,50)"><g transform="rotate(0)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="1"><animateTransform attributeName="transform" type="scale" begin="-0.875s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.875s"></animate></circle></g></g><g transform="translate(71.21320343559643,71.21320343559643)"><g transform="rotate(45)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.875"><animateTransform attributeName="transform" type="scale" begin="-0.75s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.75s"></animate></circle></g></g><g transform="translate(50,80)"><g transform="rotate(90)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.75"><animateTransform attributeName="transform" type="scale" begin="-0.625s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.625s"></animate></circle></g></g><g transform="translate(28.786796564403577,71.21320343559643)"><g transform="rotate(135)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.625"><animateTransform attributeName="transform" type="scale" begin="-0.5s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.5s"></animate></circle></g></g><g transform="translate(20,50.00000000000001)"><g transform="rotate(180)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.5"><animateTransform attributeName="transform" type="scale" begin="-0.375s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.375s"></animate></circle></g></g><g transform="translate(28.78679656440357,28.786796564403577)"><g transform="rotate(225)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.375"><animateTransform attributeName="transform" type="scale" begin="-0.25s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.25s"></animate></circle></g></g><g transform="translate(49.99999999999999,20)"><g transform="rotate(270)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.25"><animateTransform attributeName="transform" type="scale" begin="-0.125s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.125s"></animate></circle></g></g><g transform="translate(71.21320343559643,28.78679656440357)"><g transform="rotate(315)"><circle cx="0" cy="0" r="6" fill="#1d3f72" fill-opacity="0.125"><animateTransform attributeName="transform" type="scale" begin="0s" values="1.5 1.5;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform><animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="0s"></animate></circle></g></g><!-- [ldio] generated by https://loading.io/ --></svg>
				</div>
				<div class="ttw-loading-admin-messages-label-text">
					${talkToWebsiteAdminLocalization.html_loadingMessages}
				</div>
			</div>`;

			if ( typeof position === 'undefined' || position === null || position === 'after' ) {
				$( '#ttw-dialogue-block' ).append( loadingMessagesHTML ).scrollTop( $( '#ttw-dialogue-block' ).prop( 'scrollHeight' ) );
			}
			else if ( position === 'before' ) {
				$( '#ttw-dialogue-block' ).prepend( loadingMessagesHTML ).scrollTop( 1 );
			}
		},

		/**
		 * Handle admin char scrolled event
		  */
		adminDialogueTabHandleDialogueScrolled: function () {
			let scrollTopPosition = $( '#ttw-dialogue-block' ).scrollTop();
			if ( scrollTopPosition === 0 ) {
				if ( talkToWebsiteAdmin.areMoreMessagesInBackwardDirection === true && talkToWebsiteAdmin.adminMessagesCache.length > 0 ) {
					talkToWebsiteAdmin.adminDialogueTabGetAndDrawAllAdminMessages( 'backward', talkToWebsiteAdmin.adminMessagesCache[0].id );
				}
			}
		},

		/**
		 * Draw dialogues separators into admin dialogue (Testing tab)
		 */
		adminDialogueTabDrawDialoguesSeparators: function () { 
			// Try to find dialogues gaps (messages jumped from one dialogue to another)
			let $allRenderedChatMessages = $( '#ttw-dialogue-block' ).find( '.ttw-chat-message' );
			for ( let f = 0; f < $allRenderedChatMessages.length; f++ ) {
				let $currentMessageBlock = $allRenderedChatMessages.eq( f ); 
				if ( f + 1 < $allRenderedChatMessages.length ) {
					let $nextMessageBlock = $allRenderedChatMessages.eq( f + 1 );
					if ( 
						$currentMessageBlock.attr( 'data-dialogue-id' ) !== $nextMessageBlock.attr( 'data-dialogue-id' ) &&
						Number( $currentMessageBlock.attr( 'data-dialogue-id' ) ) !== -1 &&
						Number( $nextMessageBlock.attr( 'data-dialogue-id' ) ) !== -1
					) {
						let $dialogues_ids_pair = `${$currentMessageBlock.attr( 'data-dialogue-id' )}-${$nextMessageBlock.attr( 'data-dialogue-id' )}`;
						let $separator_object = $( '#ttw-dialogue-block' ).find( `.ttw-dialogue-separator[data-dialogue-ids="${$dialogues_ids_pair}"]` );
						if ( ! $separator_object || $separator_object.length === 0 ) {
							$currentMessageBlock.after( `<div class="ttw-dialogue-separator" data-dialogue-ids="${$dialogues_ids_pair}"><div class="ttw-admin-dialogue-separator-content"><span class="dashicons dashicons-admin-comments"></span> ${talkToWebsiteAdminLocalization.html_dialogueChangedTo} #${$nextMessageBlock.attr("data-dialogue-id")} (${talkToWebsiteAdminLocalization.html_dialogueChangedToTimeout})</div></div>` );
							f++;
						}
					}
				}
			}
		},

		/* --------------------------------------------------------------------- */
		/*							 History tab							   */
		/* --------------------------------------------------------------------- */

		/**
		 * Get and render dialogues for 'History' tab
		 * @param {number} offset
		 */
		historyTabGetAndDrawAllDialogues: function( offset ) {

			if ( typeof offset === 'undefined' || offset === null ) {
				offset = 0;
			}
			let limit = 25;
			talkToWebsiteAdmin.showLoader();

			$.post(
				ajaxurl, 
				{
					action: 'vappv_ttw_list_dialogues',
					data_offset: offset,
					data_limit: limit,
					nonce: vappv_ttw_admin_js.nonce
				}, 
				function( response ) {

					talkToWebsiteAdmin.hideLoader();
					let listDialoguesResponse = null;  

					// Decode response
					try {
						listDialoguesResponse = JSON.parse( response ); 
					}
					catch ( error ) {
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.listDialogues_ServerCallFailed );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_list_dialogues()] JSON exception:' );
						console.log( error );
						console.log( '--------------------------------' );
						return;
					}

					// Handle errors
					if ( listDialoguesResponse.AJAXCallResults === 'ERROR' ) {
						// Managed error
						let exceptionOutputText = talkToWebsiteAdminLocalization.listDialogues_ServerCallFailed + ' // ' + listDialoguesResponse.TTWErrorCode + '; ' + listDialoguesResponse.ErrorDetails;
						talkToWebsiteAdmin.displayError( exceptionOutputText );
						console.log( `[TalkToWebsite, ttw_try_get_message_responses_from_ai_as_admin()] Server side exception; Code = ${listDialoguesResponse.TTWErrorCode}; Details = ${listDialoguesResponse.ErrorDetails}` );
					}
					else if ( listDialoguesResponse.AJAXCallResults === 'SUCCESS' ) {

						// Get dialogues container link
						let $dialoguesContainer = $( '#ttw-dialogues-container' );
						$dialoguesContainer.empty();
							
						// Draw dialogues list
						let dialoguesList = listDialoguesResponse.Data.dialogues;
						for ( let q = 0; q < dialoguesList.length; q++ ) {
							// Prepare some params
							let dialogueObject = dialoguesList[ q ];
							if ( dialogueObject.start_date.indexOf( 'Z' ) === -1 ) {
								dialogueObject.start_date += 'Z';
							}
							let dialogueDate = new Date( dialogueObject.start_date );
							let isDialogueByAdmin = ( dialogueObject.is_thread_by_admin === true && Number( dialogueObject.is_thread_by_admin ) === 1 );
						
							// Draw HTML block
							let nextDialogueHTML = `<div class="ttw-dialogue-row ${( isDialogueByAdmin === true ? 'ttw-admin-dialogue' : '' )}" data-role="ttw-dialogue-row" data-dialogue-id="${dialogueObject.id}">
								<div class="ttw-dialogue-name-heading">${talkToWebsiteAdminLocalization.listDialogues_Dialogue} #${dialogueObject.id}</div>`;
							if ( isDialogueByAdmin === true ) {
								nextDialogueHTML += `<div class="ttw-dialogue-naming-details ttw-dialogue-with-admin">${talkToWebsiteAdminLocalization.commonText_Admin} <span class="dashicons dashicons-welcome-learn-more"></span></div>`;
							}
							else {
								nextDialogueHTML += `<div class="ttw-dialogue-naming-details ttw-dialogue-with-user"><b>${dialogueObject.user_name}</b>`;
								if ( dialogueObject.user_email && dialogueObject.user_email !== null && dialogueObject.user_email.length > 0 ) {
									nextDialogueHTML += ` / ${dialogueObject.user_email}`;
								}
								nextDialogueHTML += '</div>';
							}
							nextDialogueHTML += `<div class="ttw-dialogue-information-data-row ttw-dialogue-started-and-finished-dates">
								${talkToWebsiteAdminLocalization.listDialogues_Started}: ${talkToWebsiteAdmin.formatDateYYYYMMDD_comma_HHMM( new Date( dialogueDate ) )}`;
								if ( dialogueObject.messages.last_message_date && dialogueObject.messages.last_message_date !== null ) {
									if ( dialogueObject.messages.last_message_date.indexOf( 'Z' ) === -1) {
										dialogueObject.messages.last_message_date += 'Z';
									}
									let last_message_date = new Date( dialogueObject.messages.last_message_date );
									nextDialogueHTML += ` <span class="ttw-slash">/</span> ${talkToWebsiteAdminLocalization.listDialogues_LastMessage}: ${talkToWebsiteAdmin.formatDateYYYYMMDD_comma_HHMM( last_message_date )}`;
									nextDialogueHTML += ` <span class="ttw-slash">/</span> ${talkToWebsiteAdminLocalization.listDialogues_Duration}: ${dialogueObject.messages.dialogue_duration_minutes} ${talkToWebsiteAdminLocalization.listDialogues_DurationMinutesShort}.`;
								}
								nextDialogueHTML += `</div>
								<div class="ttw-dialogue-meta">
									${talkToWebsiteAdminLocalization.listDialogues_MessagesCount}: ${dialogueObject.messages.list.length} <span class="ttw-slash">/</span>
									${talkToWebsiteAdminLocalization.listDialogues_UserCharactersCount}: ${dialogueObject.messages.characters_from_user_count} <span class="ttw-slash">/</span>
									${talkToWebsiteAdminLocalization.listDialogues_AICharactersCount}: ${dialogueObject.messages.characters_from_ai_count}
								</div>
								<div class="ttw-dialogue-first-message-subblock" data-role="ttw-dialogue-first-message-subblock">`;
									if ( dialogueObject.messages.list.length > 0 ) {
										nextDialogueHTML += `<div class="ttw-dialogue-first-message"><span class="dashicons dashicons-editor-quote"></span> ${dialogueObject.messages.list[0].message_text}</div>`;
									}
									else {
										nextDialogueHTML += `<div class="ttw-dialogue-no-messages">${talkToWebsiteAdminLocalization.listDialogues_NoMessagesIntoDialogue}</div>`;
									}
								nextDialogueHTML += `</div>
								<div class="ttw-dialogue-messages-all-list" data-role="ttw-dialogue-messages-all-list" style="display: none;"></div>
								<div class="ttw-dialogue-buttons-bar">`;
								if ( dialogueObject.messages.list.length > 0 ) {
									nextDialogueHTML += `<div class="ttw-button ttw-button-mini" data-role="ttw-view-all-messages">${talkToWebsiteAdminLocalization.listDialogues_ViewMessages} (${dialogueObject.messages.list.length})</div>
														   <div class="ttw-button ttw-button-mini" data-role="ttw-hide-all-messages" style="display: none;">${talkToWebsiteAdminLocalization.listDialogues_HideMessages}</div>`;
								}
								nextDialogueHTML += `</div>
							</div>`;

							// Append next dialogue block
							$( '#ttw-dialogues-container' ).append( nextDialogueHTML );
						}

						// Draw pagination block
						// Draw block at the top
						let paginationBlockTop = talkToWebsiteAdmin.getPaginationBlock( listDialoguesResponse.Data.pagination, 'top' );
						$( '#ttw-dialogues-container' ).prepend( paginationBlockTop );

						// Draw block at the bottom
						let paginationBlockBottom = talkToWebsiteAdmin.getPaginationBlock( listDialoguesResponse.Data.pagination, 'bottom' );
						$( '#ttw-dialogues-container' ).append( paginationBlockBottom );

						// Append 'Refresh dialogues' button
						$dialoguesContainer.find( '[data-role="ttw-pagination"]' ).append( `<div data-role="ttw-refresh-dialogues-button" class="ttw-button ttw-button-mini ttw-green-button ttw-refresh-dialogues-button">${talkToWebsiteAdminLocalization.commonText_RefreshDialogues}</div>` );
						$dialoguesContainer.find( '[data-role="ttw-refresh-dialogues-button"]' ).off( 'click' ).on( 'click', function( e ) {
							$( 'html, body' ).animate( { scrollTop: 0 } );
							talkToWebsiteAdmin.historyTabGetAndDrawAllDialogues();
						} );

						// Subscribe events and clicks
						$dialoguesContainer.find( '[data-role="ttw-page-switcher"]' ).off( 'click' ).on( 'click', function( e ) {
							let offset = $( this ).attr( 'data-offset' );
							talkToWebsiteAdmin.historyTabGetAndDrawAllDialogues( offset );
						} );

						// Handle click on 'View all dialogue messages'
						$dialoguesContainer.find( '[data-role="ttw-view-all-messages"]' ).off( 'click' ).on( 'click', function( e ) {

							let $closestDialogueBlock = $( this ).closest( '[data-role="ttw-dialogue-row"]' );
							let dialogueId = $closestDialogueBlock.attr( 'data-dialogue-id' );

							for ( let q = 0; q < dialoguesList.length; q++ ) {

								if ( Number( dialoguesList[ q ].id) === Number( dialogueId ) ) {

									// Process dialogue messages
									let dialogueMessages = dialoguesList[ q ].messages.list;
									if ( dialogueMessages.length === 0 ) {
										talkToWebsiteAdmin.displayInfo( talkToWebsiteAdminLocalization.listDialogues_ThisDialogHasNotMessages );
										return;
									}

									// Remove 'First message' block
									$closestDialogueBlock.find( '[data-role="ttw-dialogue-first-message-subblock"]' ).hide();
									let $messagesOutputBlock = $closestDialogueBlock.find( '[data-role="ttw-dialogue-messages-all-list"]' );
									for ( let x = 0; x < dialogueMessages.length; x++ ) {

										if ( dialogueMessages[ x ].message_date.indexOf( 'Z' ) === -1 ) {
											dialogueMessages[ x ].message_date += 'Z';
										}
										dialogueMessages[ x ].user_name = dialoguesList[ q ].user_name;
										dialogueMessages[ x ].user_email = dialoguesList[ q ].user_email;
										let messageHTML = talkToWebsiteAdmin.getChatMessageHTML( dialogueMessages[ x ] );
										$messagesOutputBlock.append( messageHTML );

									}
									$messagesOutputBlock.show();

								}

							}

							// Remove button
							$( this ).hide();
							$closestDialogueBlock.find( '[data-role="ttw-hide-all-messages"]' ).show();
						} );
						
						// Handle click on 'Hide all dialogue messages'
						$dialoguesContainer.find( '[data-role="ttw-hide-all-messages"]' ).off( 'click' ).on( 'click', function( e ) {
							let $closestDialogueBlock = $( this ).closest( '[data-role="ttw-dialogue-row"]' );
							$closestDialogueBlock.find( '[data-role="ttw-view-all-messages"]' ).show();
							$closestDialogueBlock.find( '[data-role="ttw-dialogue-first-message-subblock"]' ).show();
							$closestDialogueBlock.find( '[data-role="ttw-dialogue-messages-all-list"]' ).empty().hide();
							$( this ).hide();
						} );
					}
					else {
						// Something went wrong
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.listDialogues_ServerCallFailed );
						console.log( '[TalkToWebsite, ttw_list_dialogues()]: Something went wrong / Unexpected server response.' );
					}
				}
		    ).fail( function( xhr, textStatus, errorThrown ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.listDialogues_ServerCallFailed );
				console.log( '[TalkToWebsite, ttw_list_dialogues()]: AJAX call failed.' );
			} );
		},
 
		/* --------------------------------------------------------------------- */
		/*							   Logs tab								*/
		/* --------------------------------------------------------------------- */

		/**
		 * Get and draw log entries into 'History' tab
		 * @param {number} offset
		 */
		logsTabGetAndDrawAllLogs: function( offset ) {

			if ( ! offset ) {
				offset = 0;
			}
			let limit = 15;

			// Send data to the server
			talkToWebsiteAdmin.showLoader();
			$.post(
				ajaxurl, 
				{
					action: 'vappv_ttw_get_logs',
					offset: offset,
					limit: limit,
					nonce: vappv_ttw_admin_js.nonce
				}, 
				function( response ) {
						 
					talkToWebsiteAdmin.hideLoader();
					let ttwLogsResponse = null; 

					// Decode response
					try {
						ttwLogsResponse = JSON.parse( response ); 
					}
					catch ( error ) {
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.getLogs_ServerCallFailed );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_get_logs()] JSON exception:' );
						console.log( error );
						console.log( '--------------------------------' );
						return;
					}

					// Handle errors
					if ( ttwLogsResponse.AJAXCallResults === 'ERROR' ) {
						// Managed error
						let exceptionOutputText = talkToWebsiteAdminLocalization.getLogs_ServerCallFailed + ' // ' + ttwLogsResponse.TTWErrorCode + '; ' + ttwLogsResponse.ErrorDetails;
						talkToWebsiteAdmin.displayError( exceptionOutputText );
						console.log( `[TalkToWebsite, ttw_get_logs()] Server side exception; Code = ${ttwLogsResponse.TTWErrorCode}; Details = ${ttwLogsResponse.ErrorDetails}` );
					}
					else if ( ttwLogsResponse.AJAXCallResults === 'SUCCESS' ) {
						
						// Managed success
						let $logsContainer = $( '#ttw-logs-container' );
						$logsContainer.empty();

						// -----------------------------------------------------------
						// Draw log entries
						for ( let q = 0; q < ttwLogsResponse.Data.Logs.data.length; q++ ) {
							let logObject = ttwLogsResponse.Data.Logs.data[ q ];
							if ( logObject.log_date.indexOf( 'Z' ) === -1 ) {
								logObject.log_date += 'Z';
							}

							let next_log_entry_html = `<div class="ttw-log-data-entry ttw-log-element-${logObject.log_type}">
								<div class="ttw-log-entry-heading">
									<div>${talkToWebsiteAdmin.formatDateYYYYMMDD_comma_HHMM( new Date( logObject.log_date ) )}</div>
									<div class="ttw-slash">/</div>
									<div><b ` + ( logObject.log_type === 'error' ? 'ttw-error-label' : '' ) + `>${logObject.log_type}</b></div>
								</div>
								<div class="log-entry-content">${logObject.information}</div>
							</div>`;
							$logsContainer.append( next_log_entry_html );
						}

						// -----------------------------------------------------------
						// Draw pagination, subscribe clicks
						let paginationData = ttwLogsResponse.Data.Logs.pagination;

						// Draw pagination at the top
						let paginationBlockHTML_top = talkToWebsiteAdmin.getPaginationBlock( paginationData, 'top' );
						$logsContainer.prepend( paginationBlockHTML_top );

						// Draw pagination at the bottom
						let paginationBlockHTML_bottom = talkToWebsiteAdmin.getPaginationBlock( paginationData, 'bottom' );
						$logsContainer.append( paginationBlockHTML_bottom );

						$logsContainer.find( '[data-role="ttw-page-switcher"]' ).off( 'click' ).on( 'click', function( e ) {
							let data_offset = $( this ).attr( 'data-offset' );
							talkToWebsiteAdmin.logsTabGetAndDrawAllLogs( data_offset );
						} );

						// Append 'Refresh logs' button
						$logsContainer.find( '[data-role="ttw-pagination"]' ).append( `<div data-role="ttw-refresh-logs-button" class="ttw-button ttw-button-mini ttw-green-button ttw-refresh-logs-button">${talkToWebsiteAdminLocalization.commonText_RefreshLogs}</div>` );
						$logsContainer.find( '[data-role="ttw-refresh-logs-button"]' ).off( 'click' ).on( 'click', function( e ) {
							$( 'html, body' ).animate( { scrollTop: 0 } );
							talkToWebsiteAdmin.logsTabGetAndDrawAllLogs();
						} );
					}
					else {
						// Something went wrong
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.getLogs_ServerCallFailed );
						console.log( '[TalkToWebsite, ttw_get_logs()]: Something went wrong / Unexpected server response.' );
					}
				}
			).fail( function( xhr, textStatus, errorThrown ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.getLogs_ServerCallFailed );
				console.log( '[TalkToWebsite, ttw_get_logs()]: AJAX call failed.' );
			} );
		},

		/* --------------------------------------------------------------------- */
		/*						   Quick actions bar						   */
		/* --------------------------------------------------------------------- */

		/**
		 * Enable plugin from 'Plugin is disabled' notification bar
		 */
		quickActionsEnablePlugin: function () {

			// Send data to the server
			talkToWebsiteAdmin.showLoader();
			$.post(
				ajaxurl, 
				{
					action: 'vappv_ttw_qa_enable_plugin',
					nonce: vappv_ttw_admin_js.nonce
				}, 
				function( response ) {
						 
					talkToWebsiteAdmin.hideLoader();
					let enablePluginResponse = null; 

					// Decode response
					try {
						enablePluginResponse = JSON.parse( response ); 
					}
					catch ( error ) {
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.enablePlugin_ServerCallFailed );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_qa_enable_plugin()] JSON exception:' );
						console.log( error );
						console.log( '--------------------------------' );
						return;
					}

					// Handle errors
					if ( enablePluginResponse.AJAXCallResults === 'ERROR' ) {
						// Managed error
						let exceptionOutputText = talkToWebsiteAdminLocalization.enablePlugin_ServerCallFailed + ' // ' + enablePluginResponse.TTWErrorCode + '; ' + enablePluginResponse.ErrorDetails;
						talkToWebsiteAdmin.displayError( exceptionOutputText );
						console.log( `[TalkToWebsite, ttw_qa_enable_plugin()] Server side exception; Code = ${enablePluginResponse.TTWErrorCode}; Details = ${enablePluginResponse.ErrorDetails}` );
					}
					else if ( enablePluginResponse.AJAXCallResults === 'SUCCESS' ) {
						// Managed success
						talkToWebsiteAdmin.displayInfo( talkToWebsiteAdminLocalization.enablePlugin_PluginIsEnabled );
						$( '#ttw-chatbot-disabled-plugin-top-notification' ).hide();
						$( '#ttw-chatbot-enabled-plugin-top-notification' ).show();
					}
					else {
						// Something went wrong
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.enablePlugin_ServerCallFailed );
						console.log( '[TalkToWebsite, ttw_qa_enable_plugin()]: Something went wrong / Unexpected server response.' );
					}
				}
			).fail( function( xhr, textStatus, errorThrown ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.enablePlugin_ServerCallFailed );
				console.log( '[TalkToWebsite, ttw_qa_enable_plugin()]: AJAX call failed.' );
			} );
		},

		/**
		 * Disable plugin from 'Plugin is running' notification bar
		 */
		quickActionsDisablePlugin: function () {

			// Send data to the server
			talkToWebsiteAdmin.showLoader();
			$.post(
				ajaxurl,
				{
					action: 'vappv_ttw_qa_disable_plugin',
					nonce: vappv_ttw_admin_js.nonce
				},
				function( response ) {

					talkToWebsiteAdmin.hideLoader();
					let disablePluginResponse = null;

					// Decode response
					try {
						disablePluginResponse = JSON.parse( response );
					}
					catch ( error ) {
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.disablePlugin_ServerCallFailed );
						console.log( '--------------------------------' );
						console.log( '[TalkToWebsite, ttw_qa_disable_plugin()] JSON exception:' );
						console.log( error );
						console.log( '--------------------------------' );
						return;
					}

					// Handle errors
					if ( disablePluginResponse.AJAXCallResults === 'ERROR' ) {
						// Managed error
						let exceptionOutputText = talkToWebsiteAdminLocalization.disablePlugin_ServerCallFailed + ' // ' + disablePluginResponse.TTWErrorCode + '; ' + disablePluginResponse.ErrorDetails;
						talkToWebsiteAdmin.displayError( exceptionOutputText );
						console.log( `[TalkToWebsite, ttw_qa_enable_plugin()] Server side exception; Code = ${disablePluginResponse.TTWErrorCode}; Details = ${disablePluginResponse.ErrorDetails}` );
					}
					else if ( disablePluginResponse.AJAXCallResults === 'SUCCESS' ) {
						talkToWebsiteAdmin.displayInfo( talkToWebsiteAdminLocalization.disablePlugin_PluginIsDisabled );
						$( '#ttw-chatbot-disabled-plugin-top-notification' ).show();
						$( '#ttw-chatbot-enabled-plugin-top-notification' ).hide();
					}
					else {
						// Something went wrong
						talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.disablePlugin_ServerCallFailed );
						console.log( '[TalkToWebsite, ttw_qa_disable_plugin()]: Something went wrong / Unexpected server response.' );
					}
				}
			).fail( function( xhr, textStatus, errorThrown ) {
				talkToWebsiteAdmin.displayError( talkToWebsiteAdminLocalization.disablePlugin_ServerCallFailed );
				console.log( '[TalkToWebsite, ttw_qa_disable_plugin()]: AJAX call failed.' );
			} );

		},

		/* --------------------------------------------------------------------- */
		/*							 COMMON LOGIC							  */
		/* --------------------------------------------------------------------- */

		/**
		 * Draw pagination switcher, based on VAPPV_TTW_UI_Data_Paginator.php structure
		 * @param {Object} paginationData
		 * @param {string} blockPosition
		 * @returns {string}
		 */
		getPaginationBlock: function( paginationData, blockPosition ) {

			let paginationBlockHTML = `<div data-role="ttw-pagination" data-blockposition="${blockPosition}">`;
			if ( paginationData.DrawPaginationPrefix === true ) {
				paginationBlockHTML += `<div data-offset="0" data-role="ttw-page-switcher">1</div><div data-role="ttw-pagination-dots">...</div>`;
			} 
			for ( let q = 0; q < paginationData.DisplayingPages.length; q++ ) { 
				let next_page = paginationData.DisplayingPages[ q ];
				paginationBlockHTML += `<div data-offset="${next_page.DataOffset}" data-role="ttw-page-switcher" `;
				if ( next_page.IsActivePage === true || next_page.IsActivePage === "true" || next_page.IsActivePage === "1" || next_page.IsActivePage === 1 ) {
					paginationBlockHTML += 'data-activepage="yes"';
				}
				paginationBlockHTML += `>${next_page.PageNr}</div>`;
			}
			if ( paginationData.DrawPaginationPostfix === true ) {
				paginationBlockHTML += `<div data-role="ttw-pagination-dots">...</div><div data-offset="${paginationData.PostfixDataOffset}" data-role="ttw-page-switcher">${paginationData.PagesTotalCount}</div>`;
			}
			paginationBlockHTML += '</div>';

			return paginationBlockHTML;

		},

		/**
		 * Render 'Welcome to the chat' invitation message
		 */
		renderWelcomeToTheChatMessage: function () {
			let welcomeMessageText = talkToWebsiteAdminLocalization.commonText_DefaultWelcomeText;
			if (
				$( '#ttw-settings-welcome-message-html' ).val() !== null &&
				$( '#ttw-settings-welcome-message-html' ).val().trim().length > 0
			) {
				welcomeMessageText = talkToWebsiteAdmin.prepareMessageText( $( '#ttw-settings-welcome-message-html' ).val() );
			}

			talkToWebsiteAdmin.adminDialogueTabRenderChatMessage( {
				id: -1,
				message_guid: talkToWebsiteAdmin.generateUUIDv4(),
				message_text: welcomeMessageText,
				dialogue_id: -1,
				message_date: new Date().toString(),
				sender_role: 'ai',
			}, 'prepend', true );
		},

		/**
		 * Display notification modal (information)
		 * @param {string} info_text
		 * @param {object} callback
		 */
		displayInfo: function( info_text, callback = null ) {

			// Create modal
			let infoModalTitleText = talkToWebsiteAdminLocalization.commonText_Info;
			let closeButtonText = talkToWebsiteAdminLocalization.commonText_Close;
			let modalWindowGUID = talkToWebsiteAdmin.generateUUIDv4();
			let modalWindowHTML = `<div class="ttw-notification-container" data-guid="${modalWindowGUID}" style="display: none;">
				<div class="ttw-notification ttw-notification-info">
					<div class="ttw-notification-title">${infoModalTitleText}</div>
					<div class="ttw-notification-text">${info_text}</div>
					<div class="ttw-notification-buttons">
						<div class="ttw-button ttw-button-mini" data-role="ttw-notification-ok">${closeButtonText}</div>
					</div>
				</div>
			</div>`;

			$( '#ttw-admin-section-container' ).append( modalWindowHTML );
 
			// Get links
			let $modalWindowContainer = $( '#ttw-admin-section-container' ).find( `.ttw-notification-container[data-guid="${modalWindowGUID}"]` );
			let $modalWindowContentArea = $modalWindowContainer.find( '.ttw-notification' );

			// Show modal
			$modalWindowContainer.fadeIn( 200 );

			// Adjust modal position instantly and in resizing
			talkToWebsiteAdmin.adjustModalWindowsPositioning();

			// Subscribe buttons

			// Subscribe 'Close' button
			$modalWindowContainer.find( '[data-role="ttw-notification-ok"]' ).off( 'click' ).on( 'click', function( e ) {
				$modalWindowContainer.remove();
				if ( callback && typeof callback === 'function' ) {
					callback();
				}
			} );
		},

		/**
		 * Display notification modal (error)
		 * @param {string} errorText
		 * @param {object} callback
		 */
		displayError: function( errorText, callback = null ) {

			// Create modal
			let errorModalTitleText = talkToWebsiteAdminLocalization.commonText_Error;
			let closeButtonText = talkToWebsiteAdminLocalization.commonText_Close;
			let modalWindowGUID = talkToWebsiteAdmin.generateUUIDv4();
			let modalWindowHTML = `<div class="ttw-notification-container" data-guid="${modalWindowGUID}" style="display: none;">
				<div class="ttw-notification ttw-notification-error">
					<div class="ttw-notification-title">${errorModalTitleText}</div>
					<div class="ttw-notification-text">${errorText}</div>
					<div class="ttw-notification-buttons">
						<div class="ttw-button ttw-button-mini" data-role="ttw-notification-ok">${closeButtonText}</div>
					</div>
				</div>
			</div>`;
			$( '#ttw-admin-section-container' ).append( modalWindowHTML );
 
			// Get links
			let $modalWindowContainer = $( '#ttw-admin-section-container' ).find( `.ttw-notification-container[data-guid="${modalWindowGUID}"]` );
			let $modalWindowContentArea = $modalWindowContainer.find( '.ttw-notification' ); 

			// Show modal
			$modalWindowContainer.fadeIn( 200 );

			// Adjust modal position instantly and in resizing
			talkToWebsiteAdmin.adjustModalWindowsPositioning();

			// Subscribe buttons
			$modalWindowContainer.find( '[data-role="ttw-notification-ok"]' ).off( 'click' ).on( 'click', function( e ) {
				$modalWindowContainer.remove();
				if ( callback && typeof callback === 'function' ) {
					callback();
				}
			} );
		},

		/**
		 * Adjust all opened modal windows positioning
		 */
		adjustModalWindowsPositioning: function() {

			// Get all modals needed to be adjusted
			let $allDisplayedModals = $( '#ttw-admin-section-container' ).find( '.ttw-notification-container' );
			if ( $allDisplayedModals.length > 0 ) {
				// Get link to the plugin container
				let $ttwPluginContainer = $( '#ttw-admin-section-container' );
				let overlayOffsetTop = $ttwPluginContainer.offset().top + 'px';
				let overlayOffsetLeft = $ttwPluginContainer.offset().left + 'px';

				// Itterate all modals
				$allDisplayedModals.each( function( index ) {
					// Get links
					let $modalWindowContainer = $( this );
					let $modalWindowContentArea = $modalWindowContainer.find( '.ttw-notification' );

					// Set content positioning
					let elementHeight = $modalWindowContentArea.height();
					let elementWidth = $modalWindowContentArea.width();
					let elementHeight_half = elementHeight / 2;
					let elementWidth_half = elementWidth / 2;
					$modalWindowContentArea.css( 'top', `calc(50% - ${elementHeight_half}px)` );
					$modalWindowContentArea.css( 'left', `calc(50% - ${elementWidth_half}px)` );

					// Adjust positioning according to left menu size
					let pluginContentLeftOffsetFromTheLeftMenu = $ttwPluginContainer.offset().left;
					let modalWindowCurrentPositionLeft = $modalWindowContentArea.offset().left;
					$modalWindowContentArea.css( 'left', modalWindowCurrentPositionLeft + pluginContentLeftOffsetFromTheLeftMenu / 2 );
				} );
			}
		},

		/**
		 * Display waiting loader
		 */
		showLoader: function () {  
			// First, show loader
			$( '#ttw-wait-loader-modal' ).fadeIn( 150 );

			// Then, update positioning
			talkToWebsiteAdmin.reiszeLoaderPositioning();
		},

		/**
		 * Resize loader modal positioning
		 */
		reiszeLoaderPositioning: function () {
			if ( $( '#adminmenuback' ).is( ':visible' ) && $( window ).width() > 768 ) {
				// Set position at the screen center
				let wpMenuWidth = $( '#adminmenuback' ).width();
				$( '.ttw-loader-content-block' ).css( 'left', `calc(50% - 100px)` );

				// Increment menu width
				let loaderLeftPosition = $( '.ttw-loader-content-block' ).offset().left + wpMenuWidth / 2;
				$( '.ttw-loader-content-block' ).css( 'left', loaderLeftPosition );
			}
			else { 
				$( '.ttw-loader-content-block' ).css( 'left', `calc(50% - 100px)` );
			}

			let waitLoaderNewTopSummaryPosition = $( '.ttw-plugin-title-header' ).position().top + $( '.ttw-plugin-title-header' ).outerHeight( true );
			//$( '#ttw-wait-loader-modal' ).css( 'top', `${waitLoaderNewTopSummaryPosition}px` );
			$( '#ttw-wait-loader-modal' ).css( 'top', 0 );
		},

		/**
		 * Hide waiting loader
		 */
		hideLoader: function () {
			$( '#ttw-wait-loader-modal' ).stop().fadeOut( 150 );
		}
	};

	// Make environment visible for outer access
	window.talkToWebsiteAdmin = talkToWebsiteAdmin;

	// Initialize plugin admin part after page loaded
	$( function () {
		talkToWebsiteAdmin.initializeAdmin();
	} );

} )( jQuery );