/**
 * Meta info manager
 * 
 * @package JMAP::METAINFO::administrator::components::com_jmap
 * @subpackage js
 * @author Joomla! Extensions Store
 * @copyright (C) 2021 Joomla! Extensions Store
 * @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html
 */
//'use strict';
(function($) {
	var Metainfo = function() {
		/**
		 * Target sitemap link
		 * 
		 * @access private
		 * @var String
		 */
		var targetSitemapLink = null;
		
		/**
		 * Message timeout handler
		 * 
		 * @access private
		 * @var Object
		 */
		var msgTimeout = null;
		
		/**
		 * Promises array
		 * 
		 * @access private
		 * @var Array
		 */
		var promisesCollection = new Array();
		
		/**
		 * Promises array collection for saving multiple records
		 * 
		 * @access private
		 * @var Array
		 */
		var savePromisesCollection = new Array();
		
		/**
		 * Max title length on desktop
		 * 
		 * @access private
		 * @var Integer
		 */
		var maxLengthTitleDesktop = 573;
		
		/**
		 * Max description length on desktop
		 * 
		 * @access private
		 * @var Integer
		 */
		var maxLengthDescriptionDesktop = 930;

		/**
		 * Max title length on mobile
		 * 
		 * @access private
		 * @var Integer
		 */
		var maxLengthTitleMobile = 550;
		
		/**
		 * Max description length on mobile
		 * 
		 * @access private
		 * @var Integer
		 */
		var maxLengthDescriptionMobile = 905;

		/**
		 * Title font on desktop
		 * 
		 * @access private
		 * @var String
		 */
		var titleFontDesktop = '20px Arial,sans-serif';
		
		/**
		 * Description font on desktop
		 * 
		 * @access private
		 * @var String
		 */
		var descriptionFontDesktop = '14px Arial,sans-serif';

		/**
		 * Title font on mobile
		 * 
		 * @access private
		 * @var String
		 */
		var titleFontMobile = '16px Roboto';
		
		/**
		 * Description font on mobile
		 * 
		 * @access private
		 * @var String
		 */
		var descriptionFontMobile = '14px Roboto';
		
		/**
		 * Bind the media popovers to images for FB debugger
		 * 
		 * @access private
		 * @param String selector
		 * @return void 
		 */
		var bindMediaPopovers = function(selector) {
			setTimeout(function(){
				$(selector).each(function(index, element){
					var $element = $(element);
					if($element.next('input').val() != '') {
						$element.attr('data-original-title', COM_JMAP_OPEN_FB_DEBUGGER);
					}
				});
			}, 1);
		};
		
		/**
		 * Open first operation progress bar
		 * 
		 * @access private
		 * @return void 
		 */
		var showMessages = function(message, state) {
			var messageSnippet = '<div id="jmap_alert_message" class="alert alert-' + state + '">' +
						            '<span class="glyphicon glyphicon-info-sign"></span><span class="alert-message"> ' + message + '</span>' +
						          '</div>';		
			
			clearTimeout(msgTimeout);
			$('#jmap_alert_message').remove();
			
			$('#alert_append').append(messageSnippet);
			$('#jmap_alert_message').fadeIn('fast');
			
			var timerReady = $.Deferred();
			$.when(timerReady).done(function(response){
				$('#jmap_alert_message').fadeOut('fast', function(){
					$('#jmap_alert_message').remove();
				});
			});
			
			msgTimeout = setTimeout(function(){
				timerReady.resolve();
			}, 3000);
		};
		
		/**
		 * Parse url to grab query string params to post to server side for sitemap generation
		 * 
		 * @access private
		 * @return Object
		 */
		var parseURL = function(url) {
		    var a =  document.createElement('a');
		    a.href = url;
		    return {
		        source: url,
		        protocol: a.protocol.replace(':',''),
		        host: a.hostname,
		        port: a.port,
		        query: a.search,
		        params: (function(){
		            var ret = {},
		                seg = a.search.replace(/^\?/,'').split('&'),
		                len = seg.length, i = 0, s;
		            for (;i<len;i++) {
		                if (!seg[i]) { continue; }
		                s = seg[i].split('=');
		                ret[s[0]] = s[1];
		            }
		            return ret;
		        })(),
		        file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
		        hash: a.hash.replace('#',''),
		        path: a.pathname.replace(/^([^\/])/,'/$1'),
		        relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
		        segments: a.pathname.replace(/^\//,'').split('/')
		    };
		}
		
		/**
		 * Register user events for interface controls
		 * 
		 * @access private
		 * @param Boolean initialize
		 * @return Void
		 */
		var addListeners = function(initialize) {
			// Start the generation process, first operation is enter the progress modal mode
			$('a.jmap_metainfo').on('click.metainfo', function(jqEvent){
				// Prevent click link default
				jqEvent.preventDefault();
				
				// Show striped progress started generation
				showProgress(true, 50, 'striped', COM_JMAP_METAINFO_STARTED_SITEMAP_GENERATION);
				
				// Grab targeted sitemap link
				targetSitemapLink = $(this).attr('href');
			});
			
			// Register form submit event
			$('#adminForm ul.pagination-list li').filter(function(){
				if($(this).hasClass('active') || $(this).hasClass('disabled')) {
					return false;
				}
				return true;
			}).on('click.metainfo', function(jqEvent){
				// Show striped progress started generation
				showProgress(true, 100, 'striped', COM_JMAP_METAINFO_ANALYZING_LINKS);
			});
			$('#limit').on('change.metainfo', function(jqEvent){
				showProgress(true, 100, 'striped', COM_JMAP_METAINFO_ANALYZING_LINKS);
			});
			$('#adminForm table.adminlist th a.hasTooltip').on('click.metainfo', function(jqEvent){
				// Show striped progress started generation
				showProgress(true, 100, 'striped', COM_JMAP_METAINFO_ANALYZING_LINKS);
			});
			
			// Register button task actions
			// Save metainfo
			$('#adminForm button[data-action=savemeta]').on('click.metainfo', function(jqEvent) {
				// Prevent button default
				jqEvent.preventDefault();
				
				// Retrive information to save
				var rowIdentifier = $(this).data('save');
				var linkIdentifier = $('a[data-linkidentifier=' + rowIdentifier + ']').attr('href');
				var metaTitle = $('textarea[data-titleidentifier=' + rowIdentifier + ']').val();
				var metaDesc = $('textarea[data-descidentifier=' + rowIdentifier + ']').val();
				var metaImage = $('input[data-mediaidentifier=' + rowIdentifier + '], #jform_media_identifier_' + rowIdentifier).val();
				var robotsDirective = $('select[data-robotsidentifier=' + rowIdentifier + ']').val();
				var publishedStatus = $('input[name=published' + (rowIdentifier-1) + ']:checked').prop('value');
				var excludedStatus = $('input[name=excluded' + (rowIdentifier-1) + ']:checked').prop('value');

				// Perform validation, at least one of title/desc/robots
				// must be a valid value
				if (!metaTitle && !metaDesc && !robotsDirective && !metaImage) {
					showMessages(COM_JMAP_METAINFO_SET_ATLEAST_ONE, 'warning');
					return false;
				}

				// Now build the object to send to server endpoint
				var dataObject = {
					linkurl : linkIdentifier,
					meta_title : metaTitle,
					meta_desc : metaDesc,
					meta_image : metaImage,
					robots : robotsDirective,
					published : publishedStatus,
					excluded : excludedStatus
				};
				
				// Now save to server side
				saveDataStatus('saveMeta', dataObject, rowIdentifier);
				
				return false;
			});
			
			// Delete metainfo record
			$('#adminForm button[data-action=deletemeta]').on('click.metainfo', function(jqEvent) {
				// Prevent button default
				jqEvent.preventDefault();
				
				// Retrive information to save
				var rowIdentifier = $(this).data('delete');
				var linkIdentifier = $('a[data-linkidentifier=' + rowIdentifier + ']').attr('href');
				
				// Now build the object to send to server endpoint
				var dataObject = {
					linkurl : linkIdentifier
				};
				
				// Now save to server side
				saveDataStatus('deleteMeta', dataObject);
				
				// Now reset row data
				var parentRow = $(this).parents('tr');
				$('textarea.metainfo, select.robots_directive, input.mediaimagefield', parentRow).val('');
				$('textarea.metainfo', parentRow).text('');
				rowButtonStatus(parentRow);
				
				// Now reset characters counter
				$('span.labelmetainfo span.jmap_metainfo_counter', parentRow).text(0);
				$('span.labelmetainfo span.jmap_metainfo_counter_pixel', parentRow).text('0px');
				// Reset also a possible danger class
				$('span.labelmetainfo', parentRow).removeClass('label-danger').addClass('label-primary');
				
				// Finally reset the exclude state in the case there was one. Take care that the record is completely deleted
				$('fieldset[data-action=excludedmeta] div.controls label:first-child', parentRow).trigger('click', [true]);
				
				return false;
			});
			
			// Change metainfo record state
			$('#adminForm fieldset[data-action=statemeta] label').on('click.metainfo', function(jqEvent, noTrigger) {
				// Avoid triggering if not user click
				if(noTrigger || jqEvent.target.nodeName == 'INPUT') {
					return true;
				}
				
				// Retrive information to save
				var rowIdentifier = $(this).parents('fieldset').data('state');
				var linkIdentifier = $('a[data-linkidentifier=' + rowIdentifier + ']').attr('href');
				var publishedState = $(this).children('input').val();
				
				// Now build the object to send to server endpoint
				var dataObject = {
					linkurl : linkIdentifier,
					field : 'published',
					fieldValue : parseInt(publishedState)
				};
				
				// Now save to server side
				saveDataStatus('stateMeta', dataObject);
				
				return true;
			});
			
			$('#adminForm fieldset[data-action=excludedmeta] label').on('click.metainfo', function(jqEvent, noTrigger) {
				// Avoid triggering if not user click
				if(noTrigger || jqEvent.target.nodeName == 'INPUT') {
					return true;
				}
				
				// Retrive information to save
				var parentRow = $(this).parents('tr');
				var rowIdentifier = $(this).parents('fieldset').data('state');
				var linkIdentifier = $('a[data-linkidentifier=' + rowIdentifier + ']').attr('href');
				var excludedState = $(this).children('input').val();
				
				// Now build the object to send to server endpoint
				var dataObject = {
					linkurl : linkIdentifier,
					field : 'excluded',
					fieldValue : parseInt(excludedState) 
				};
				
				// Now save to server side
				saveDataStatus('stateMeta', dataObject);
				
				// Switch by default to unpublished if it's a new empty record store
				if($('fieldset[data-action=statemeta] div.controls label:first-child', parentRow).hasClass('jmap_inactive')) {
					$('fieldset[data-action=statemeta] div.controls label:first-child', parentRow).trigger('click', [true]);
				}
				
				return true;
			});
			
			// Generate metainfo based on the ChatGPT API button
			$(document).on('click.metainfo', '#adminForm button.jmap-chatgpt-metagenerator', function(jqEvent) {
				jqEvent.preventDefault();
				jqEvent.stopPropagation();
				
				// Retrive information to save
				var metadataType = '';
				var parentTD = $(this).parents('td');
				var textareaIdentifier = $('textarea', parentTD);
				
				if($(textareaIdentifier).attr('data-titleidentifier')) {
					var rowIdentifier = $(textareaIdentifier).data('titleidentifier');
					metadataType = 'title';
				} else if($(textareaIdentifier).attr('data-descidentifier')) {
					var rowIdentifier = $(textareaIdentifier).data('descidentifier');
					metadataType = 'description';
				}
				
				var linkIdentifier = $('a[data-linkidentifier=' + rowIdentifier + ']').attr('href');
				var sitemapLanguage = $('#adminForm input[name=sitemaplanguage]').val();
				
				// Now build the object to send to server endpoint
				var dataObject = {
					linkurl : linkIdentifier,
					type : metadataType,
					sitemaplang : sitemapLanguage
				};
				
				// Now save to server side
				generateChatGPTMeta(dataObject, textareaIdentifier);
				
				return false;
			});
			
			// Bind an event handler for textarea writing, used to count characters and lock/unlock disabled buttons
			$('table.adminlist tbody textarea, table.adminlist tbody input').on('keyup.metainfo', function(jqEvent) {
				var parentRow = $(this).parents('tr');
				// Update notify button status callback
				rowButtonStatus(parentRow);
			});
			
			$('table.adminlist tbody input[type=text]').on('change.metainfo input.metainfo propertychange.metainfo', function(jqEvent) {
				var parentRow = $(this).parents('tr');
				// Update notify button status callback
				rowButtonStatus(parentRow);
			});
			
			// Required for Joomla 3.5+
			$('table.adminlist tbody button.button-clear, table.adminlist tbody a.button-clear').on('click.metainfo', {context:this}, function(jqEvent) {
				var parentRow = $(this).parents('tr');
				var indentifier = $('td.link_loc a', parentRow).data('linkidentifier');
				// Update notify button status callback
				setTimeout(function(context){
					context.refreshRowStatus(parentRow, indentifier);
				}, 1, jqEvent.data.context)
			});
			
			// Required for Joomla 3.7+ in order to clear the modal-body and avoid multiple iframes append
			$('*[id*=imageModal_jform_media_identifier]').on('hide.bs.modal', function(){
				$('div.modal-body', this).empty();
			});
			
			$('table.adminlist tbody select.robots_directive').on('change.metainfo', function(jqEvent) {
				var parentRow = $(this).parents('tr');
				// Update notify button status callback
				rowButtonStatus(parentRow);
			});

			// Live event binding only once on initialize, avoid repeated
			// handlers and executed callbacks
			if (initialize) {
				// Live event binding for close button AKA stop process
				$(document).on('click.metainfo', 'label.closeprecaching', function(jqEvent) {
					$('#metainfo_process').modal('hide');
				});
			}
			
			// Attach save all event listener
			$('#toolbar-save button').on('click', function(jqEvent){
				jqEvent.preventDefault();
			
				// Start processing
				$('button[data-action=savemeta].jmap_modified:not([disabled])').trigger('click.metainfo');
				
				// When all promises are resolved start the async duplicated title/desc count
				$.when.apply($, savePromisesCollection).then(function() {
					showMessages(COM_JMAP_ALL_METAINFO_SAVED, 'success');
				});
				
				return false;
			});
			
			// Attach auto populate event listener
			$('#toolbar-database button').on('click', function(jqEvent){
				jqEvent.preventDefault();
			
				// Start processing
				autoPopulateMetainfo();
				
				return false;
			});
			
			// Facebook debugger link open
			$(document).on('click', 'span.field-media-preview', function(jqEvent){
				var nextInputValue = $(this).nextAll('input.field-media-input').val();
				if(nextInputValue) {
					var rowLinkIdentifier = $('td.link_loc a[data-role=link]', $(this).parents('tr')).attr('href');
					window.open('https://developers.facebook.com/tools/debug/?q=' + encodeURIComponent(rowLinkIdentifier) , '_blank');
				}
			});
			
			// Bind popovers to the media img field
			bindMediaPopovers('span.field-media-preview');
			
			$('input.mediaimagefield').on('change', function(jqEvent){
				bindMediaPopovers('span.field-media-preview');
			});
		};
		
		/**
		 * Show progress dialog bar with informations about the ongoing started
		 * process
		 * 
		 * @access private
		 * @return Void
		 */
		var showProgress = function(isNew, percentage, type, status, classColor) {
			// No progress process injected
			if(isNew) {
				// Show progress
				var progressBar = '<div class="progress progress-' + type + ' active">' +
										'<div id="progress_bar" class="progress-bar" role="progressbar" aria-valuenow="' + percentage + '" aria-valuemin="0" aria-valuemax="100">' +
											'<span class="sr-only"></span>' +
										'</div>' +
									'</div>';
				
				// Build modal dialog
				var modalDialog =	'<div class="modal fade" id="metainfo_process" tabindex="-1" role="dialog" aria-labelledby="progressModal" aria-hidden="true">' +
										'<div class="modal-dialog">' +
											'<div class="modal-content">' +
												'<div class="modal-header">' +
									        		'<h4 class="modal-title">' + COM_JMAP_METAINFO_TITLE + '</h4>' +
									        		'<label class="closeprecaching glyphicon glyphicon-remove-circle"></label>' +
									        		'<p class="modal-subtitle">' + COM_JMAP_METAINFO_PROCESS_RUNNING + '</p>' +
								        		'</div>' +
								        		'<div class="modal-body">' +
									        		'<p>' + progressBar + '</p>' +
									        		'<p id="progress_info">' + status + '</p>' +
								        		'</div>' +
								        		'<div class="modal-footer">' +
									        	'</div>' +
								        	'</div><!-- /.modal-content -->' +
							        	'</div><!-- /.modal-dialog -->' +
							        '</div>';
				// Inject elements into content body
				$('body').append(modalDialog);
				
				// Setup modal
				var modalOptions = {
						backdrop:'static'
					};
				$('#metainfo_process').modal(modalOptions);
				
				// Async event progress showed and styling
				$('#metainfo_process').on('shown.bs.modal', function(event) {
					$('#metainfo_process div.modal-body').css({'width':'90%', 'margin':'auto'});
					$('#progress_bar').css({'width':percentage + '%'});
					
					// Start AJAX GET request for sitemap generation in the cache folder
					startSitemapCaching(targetSitemapLink);
				});
				
				// Remove backdrop after removing DOM modal
				$('#metainfo_process').on('hidden.bs.modal',function(jqEvent){
					$('.modal-backdrop').remove();
					$(this).remove();
					
					// Redirect to MVC core cpanel, discard metainfo process
					window.location.href = 'index.php?option=com_jmap&task=cpanel.display'
				});
			} else {
				// Refresh only status, progress and text
				$('#progress_bar').addClass(classColor)
								  .css({'width':percentage + '%'});
				
				$('#progress_bar').parent().removeClass('progress-normal progress-striped')
								  .addClass('progress-' + type);
				
				$('#progress_info').html(status);		
				
				// An error has been detected, so auto close process and progress bar
				if(classColor == 'progress-bar-danger') {
					setTimeout(function(){
						$('#metainfo_process').modal('hide');
					}, 3500);
				}
			}
		}
		
		/**
		 * The first operation is get informations about published data sources
		 * and start cycle over all the records using promises and recursion
		 * 
		 * @access private
		 * @param String targetSitemapLink
		 * @return Void
		 */
		var startSitemapCaching = function(targetSitemapLink) {
			// No ajax request if no control panel generation in 2 steps
			if(!targetSitemapLink) {
				return;
			}
			// Request JSON2JSON
			var dataSourcePromise = $.Deferred(function(defer) {
				$.ajax({
					type : "GET",
					url : targetSitemapLink,
					dataType : 'json',
					context : this,
					data: {'metainfojsclient' : true}
				}).done(function(data, textStatus, jqXHR) {
					if(!data.result) {
						// Error found
						defer.reject(COM_JMAP_METAINFO_ERROR_STORING_FILE, textStatus);
						return false;
					}
					
					// Check response all went well
					if(data.result) {
						defer.resolve();
					}
				}).fail(function(jqXHR, textStatus, errorThrown) {
					// Error found
					var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1);
					defer.reject('-' + genericStatus + '- ' + errorThrown);
				});
			}).promise();

			dataSourcePromise.then(function() {
				// Update process status, we started
				showProgress(false, 100, 'striped', COM_JMAP_METAINFO_GENERATION_COMPLETE, 'progress-normal');
				
				// Parse sitemap parameters
				var sitemapParams = parseURL(targetSitemapLink).params;
				var sitemapLang = sitemapParams.lang ? '&sitemaplang=' + sitemapParams.lang : '';
				var sitemapDataset = sitemapParams.dataset ? '&sitemapdataset=' + sitemapParams.dataset : '';
				var sitemapMenuID = sitemapParams.Itemid ? '&sitemapitemid=' + sitemapParams.Itemid : '';
				
				// Redirect to MVC core
				window.location.href = 'index.php?option=com_jmap&task=metainfo.display&metainfojsclient=1' + sitemapLang + sitemapDataset + sitemapMenuID;
			}, function(errorText, error) {
				// Do stuff and exit
				showProgress(false, 100, 'normal', errorText, 'progress-bar-danger');
			});
		};
		
		/**
		 * Manage the data saving and the status change for each sitemap record
		 * in the model database table
		 * 
		 * @access private
		 * @param String action
		 * @param String dataObject
		 * @param Int rowIdentifier
		 * @return Void
		 */
		var saveDataStatus = function(action, dataObject, rowIdentifier) {
			// Object to send to server
			var ajaxparams = {
				idtask : action,
				param: dataObject
			};

			// Unique param 'data'
			var uniqueParam = JSON.stringify(ajaxparams);
			
			// Request JSON2JSON
			var metainfoPromise = $.Deferred(function(defer) {
				$.ajax({
					type : "POST",
					url: "../administrator/index.php?option=com_jmap&task=ajaxserver.display&format=json",
					dataType : 'json',
					context : this,
					data : {
						data : uniqueParam
					}
				}).done(function(data, textStatus, jqXHR) {
					if(!data.result) {
						// Error found
						defer.reject(data.exception_message, textStatus);
						return false;
					}
					
					// Check response all went well
					if(data.result) {
						var userMessage = data.exception_message || COM_JMAP_METAINFO_SAVED;
						defer.resolve(userMessage, data);
					}
				}).fail(function(jqXHR, textStatus, errorThrown) {
					// Error found
					var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1);
					defer.reject('-' + genericStatus + '- ' + errorThrown);
				});
			}).promise();

			metainfoPromise.then(function(message, dataResponse) {
				// Update process status, we started
				if(action == 'stateMeta' && dataResponse.result && dataResponse.exception_message) { } else {
					showMessages(message, 'success');
				}
			}, function(errorText, error) {
				// Do stuff and exit
				showMessages(errorText, 'error');
			});

			// Assign the promise to the collection
			if(rowIdentifier) {
				savePromisesCollection[rowIdentifier] = metainfoPromise;
			}
		};
		
		/**
		 * Manage the data saving and the status change for each sitemap record
		 * in the model database table
		 * 
		 * @access private
		 * @param String dataObject
		 * @param HTMLElement targetTextArea
		 * @return Void
		 */
		var generateChatGPTMeta = function(dataObject, targetTextArea) {
			// Object to send to server
			var ajaxparams = {
				idtask : 'generateChatGPTMeta',
				param: dataObject,
				DIModels: [
					{
						'modelside' : 1,
						'modelname' : 'Aigenerator'
					},
					{
						'modelside' : 1,
						'modelname' : 'Metainfo'
					}
				]
			};

			// Store the row and column context
			var parentColumn = $(targetTextArea).parents('td');
			var parentRow = $(targetTextArea).parents('tr');
			
			// Set the working icon
			$('button.jmap-chatgpt-metagenerator img.jmap-chatgpt-icon', parentColumn).attr('src', jmap_baseURI + 'administrator/components/com_jmap/images/loading.gif');
			
			// Unique param 'data'
			var uniqueParam = JSON.stringify(ajaxparams);
			
			// Request JSON2JSON
			var metainfoPromise = $.Deferred(function(defer) {
				$.ajax({
					type : "POST",
					url: "../administrator/index.php?option=com_jmap&task=ajaxserver.display&format=json",
					dataType : 'json',
					context : this,
					data : {
						data : uniqueParam
					}
				}).done(function(data, textStatus, jqXHR) {
					if(!data.result) {
						// Error found
						defer.reject(data.exception_message, textStatus);
						return false;
					}
					
					// Check response all went well
					if(data.result) {
						var resultMessage = data.exception_message || COM_JMAP_AIGENERATOR_METAINFO_GENERATED;
						defer.resolve(resultMessage, data);
					}
				}).fail(function(jqXHR, textStatus, errorThrown) {
					// Error found
					var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1);
					defer.reject('-' + genericStatus + '- ' + errorThrown);
				});
			}).promise();

			metainfoPromise.then(function(message, dataResponse) {
				// Update process status, we started
				if(dataResponse.result && dataResponse.exception_message) { } else {
					showMessages(message, 'success');
					
					// Populate the just generated metadata in the matching field
					$(targetTextArea).text(dataResponse.generated);
					$(targetTextArea).val(dataResponse.generated);
					
					// Update refresh status
					rowButtonStatus(parentRow);
					$(targetTextArea).trigger('keyup.metainfo');
				}
			}, function(errorText, error) {
				// Do stuff and exit
				showMessages(errorText, 'error');
			}).always(function(){
				$('button.jmap-chatgpt-metagenerator img.jmap-chatgpt-icon', parentColumn).attr('src', jmap_baseURI + 'administrator/components/com_jmap/images/chatgpt-logo.svg');
			});
		};
		
		/**
		 * Evaluate and keep synced the enabled buttons status based on the current row values
		 * 
		 * @access private
		 * @param specificRow
		 * @return Void
		 */
		var rowButtonStatus = function(specificRow) {
			// Search for each row if at least one of the 3 values is specified and only in that case enable buttons
			var finalSelector = specificRow || 'table.adminlist tbody tr';
			
			$(finalSelector).each(function(index, tableRow){
				var titleValue = $('textarea.metatitle', tableRow).val();
				var descValue = $('textarea.metadesc', tableRow).val();
				var imageValue = $('input.mediaimagefield', tableRow).val();
				var robotsValue = $('select.robots_directive', tableRow).val();
				
				// If none of the value are available disable buttons
				if(!titleValue && !descValue && !imageValue && !robotsValue) {
					$('button:not(.button-select):not(.close):not(.jmap-chatgpt-metagenerator), fieldset.radio[data-action=statemeta] label', tableRow).attr('disabled', true);
					$('fieldset.radio[data-action=statemeta] label', tableRow).addClass('jmap_inactive');
					if(specificRow) {
						$('button[data-action=savemeta]', tableRow).removeClass('jmap_modified');
					}
				} else {
					$('button, fieldset.radio[data-action=statemeta] label', tableRow).removeAttr('disabled');
					$('fieldset.radio[data-action=statemeta] label', tableRow).removeClass('jmap_inactive');
					if(specificRow) {
						$('button[data-action=savemeta]', tableRow).addClass('jmap_modified');
					}
				}
			});
		};
		
		/**
		 * Extend the jQuery prototype with a plugin to count and validate
		 * the length of the textarea characters
		 */
		var addTextareaLengthvalidation = function() {
			// jQuery prototype
			$.fn.textWidth = function(text, font) {
				if (!$.fn.textWidth.fakeEl) {
					$.fn.textWidth.fakeEl = $('<span class="jmapmetainfo_fake_element">').appendTo(document.body);
				}
				$.fn.textWidth.fakeEl.text(text).css('font', font);
				return parseInt($.fn.textWidth.fakeEl.width());
			};
			
			$.fn.jmapCharacterCount = function charCount(options) {
				var defaults = {
						limit : 100
				};
				var options = $.extend({}, defaults, options);
				if (this.length) {
					return $(this).each(function charCountEachElement() {
						var $this = $(this);
						var addElements = function() {
							$this.next('img.hasTooltip').remove();
							$this.container = $('<span/>');
							$this.counterCharacter = $('<span/>').attr('title', COM_JMAP_CHARACTERS).addClass('label labelmetainfo').addClass('label-primary').html( '<span class="jmapmetainfo_chars_count glyphicon glyphicon-align-left"></span><span class="jmap_metainfo_counter">' + 0 + '</span>'); 
							$this.counterPxDesktop = $('<span/>').attr('title', COM_JMAP_PIXEL_DESKTOP).addClass('label labelmetainfo').addClass('label-primary').html( '<span class="jmapmetainfo_pixel_count glyphicon glyphicon-blackboard"></span><span class="jmap_metainfo_counter_pixel">' + 0 + 'px</span>');
							$this.counterPxMobile = $('<span/>').attr('title', COM_JMAP_PIXEL_MOBILE).addClass('label labelmetainfo').addClass('label-primary').html( '<span class="jmapmetainfo_pixel_count glyphicon glyphicon-phone"></span><span class="jmap_metainfo_counter_pixel">' + 0 + 'px</span>');
							
							$this.after($this.container);
							$this.container.append($this.counterCharacter);
							$this.container.append($this.counterPxDesktop);
							$this.container.append($this.counterPxMobile);
							
							// ChatGPT generation to enable based by settings
							if(jmap_chatgptGeneration) {
								if($this.hasClass('metatitle')) {
									var chatGPTTooltip = 'Click to generate the meta title with ChatGPT';
								} else {
									var chatGPTTooltip = 'Click to generate the meta description with ChatGPT';
								}
								$this.chatgptGenerationButton = $('<button class="jmap-chatgpt-metagenerator" title="' + chatGPTTooltip + '"><img class="jmap-chatgpt-icon" src="' + jmap_baseURI + 'administrator/components/com_jmap/images/chatgpt-logo.svg" alt="ChatGPT"></button>');
								
								$this.container.append($this.chatgptGenerationButton);
							}
						};
						$this.update = function() {
							var length = $this.val().length;
							if($this.hasClass('metatitle')) {
								var desktopLength = $.fn.textWidth($this.val(), titleFontDesktop);
								var mobileLength = $.fn.textWidth($this.val(), titleFontMobile);
							} else if($this.hasClass('metadesc')) {
								var desktopLength = $.fn.textWidth($this.val(), descriptionFontDesktop);
								var mobileLength = $.fn.textWidth($this.val(), descriptionFontMobile);
							}
							$this.counterCharacter.html('<span title="' + COM_JMAP_CHARACTERS + '" class="jmapmetainfo_chars_count glyphicon glyphicon-align-left"></span><span class="jmap_metainfo_counter">' + length + '</span>');
							$this.counterPxDesktop.html('<span title="' + COM_JMAP_PIXEL_DESKTOP + '" class="jmapmetainfo_pixel_count glyphicon glyphicon-blackboard"></span><span class="jmap_metainfo_counter_pixel">' + desktopLength + 'px</span>');
							$this.counterPxMobile.html('<span title="' + COM_JMAP_PIXEL_MOBILE + '" class="jmapmetainfo_pixel_count glyphicon glyphicon-phone"></span><span class="jmap_metainfo_counter_pixel">' + mobileLength + 'px</span>');
							// Warning class for the characters count
							if (options.limit < length) {
								$this.counterCharacter.removeClass('label-primary');
								$this.counterCharacter.addClass('label-danger');
							} else {
								$this.counterCharacter.removeClass('label-danger');
								$this.counterCharacter.addClass('label-primary');
							}
							// Warning class for title pixel count
							if($this.hasClass('metatitle')) {
								if (maxLengthTitleDesktop < desktopLength) {
									$this.counterPxDesktop.removeClass('label-primary');
									$this.counterPxDesktop.addClass('label-danger');
								} else {
									$this.counterPxDesktop.removeClass('label-danger');
									$this.counterPxDesktop.addClass('label-primary');
								}
								if (maxLengthTitleMobile < mobileLength) {
									$this.counterPxMobile.removeClass('label-primary');
									$this.counterPxMobile.addClass('label-danger');
								} else {
									$this.counterPxMobile.removeClass('label-danger');
									$this.counterPxMobile.addClass('label-primary');
								}
							}
							// Warning class for description pixel count
							if($this.hasClass('metadesc')) {
								if (maxLengthDescriptionDesktop < desktopLength) {
									$this.counterPxDesktop.removeClass('label-primary');
									$this.counterPxDesktop.addClass('label-danger');
								} else {
									$this.counterPxDesktop.removeClass('label-danger');
									$this.counterPxDesktop.addClass('label-primary');
								}
								if (maxLengthDescriptionMobile < mobileLength) {
									$this.counterPxMobile.removeClass('label-primary');
									$this.counterPxMobile.addClass('label-danger');
								} else {
									$this.counterPxMobile.removeClass('label-danger');
									$this.counterPxMobile.addClass('label-primary');
								}
							}
						};
						$this.on('keyup.metainfo', function(event) {
							$this.update();
						});
						addElements();
						$this.update();
					});
				}
			};
		};
		
		/**
		 * Process the asyncronous analysis of links
		 * Auto populates the metainfo links records providing the auto click on the save button
		 *
		 * @access private
		 * @return Void
		 */
		var autoPopulateMetainfo = function() {
			// Retrieve all the links to analyze on page
			var linksToAnalyze = $('a[data-role=link]');
			
			// No ajax request if no links to analyze
			if(!linksToAnalyze.length) {
				return;
			}

			$.each(linksToAnalyze, function(index, link){
				var targetCrawledLink = $('a[data-role="link"]').get(index);
				var targetTitle = $('textarea[data-bind="{title}"]').get(index);
				var targetDesc = $('textarea[data-bind="{desc}"]').get(index);
				var targetRobots = $('select[data-bind="{robots}"]').get(index);
				var targetSocialImage = $('input.field-media-input.mediaimagefield').get(index);
				
				// Preappend a loading progress waiter to each processed link
				$(targetCrawledLink).after('<img class="metainfo_loader" width="16px" height="16px" src="' + jmap_baseURI + 'administrator/components/com_jmap/images/loading.gif' + '"/>');
				
				promisesCollection[index] = $.Deferred(function(defer) {
					setTimeout(function(){
						$.ajax({
							type : "GET",
							url : $(link).attr('href'),
						}).done(function(data, textStatus, jqXHR) {
							// Check response HTTP status code
							defer.resolve(data, jqXHR.status);
						}).fail(function(jqXHR, textStatus, errorThrown) {
							// Error found
							defer.resolve(null, jqXHR.status);
						});
					}, index * jmap_crawlerDelay);
				}).promise();
				
				promisesCollection[index].then(function(responseData, status) {
					// Always remove the waiter
					$(targetCrawledLink).next('img.metainfo_loader').remove();
					
					// STEP 1 - If the status validation is not 200 OK simply skip to next link promise
					if(status != 200) {
						return;
					}
					
					// Set init status for this row iteration
					var titlePopulated = false;
					var descPopulated = false;
					var robotsPopulated = false;
					var socialImagePopulated = false;
					
					// Set the parsed wrapped set
					var responseDataWrappedSet = $(responseData.trim());

					// STEP 2 - Title retrieval and reporting
					var title = responseDataWrappedSet.filter('title').text().trim();
					// Remove the site name suffix/prefix part in the title if any
					if(jmap_siteNamePageTitles > 0) {
						switch (jmap_siteNamePageTitles) {
							// Prepended before prefix
							case 1:
								title = title.replace(jmap_siteName + ' - ', '');
								break;
							
							// Appended after suffix
							case 2:
								title = title.replace(' - ' + jmap_siteName, '');
								break;
						}
					}
					
					// Manage title validity, preserve existing values
					var existingTargetTitle = $(targetTitle).text();
					if(title && !existingTargetTitle && jmap_metainfoAutoGenerateMetatitle != 2) {
						$(targetTitle).text( title ).val( title );
						titlePopulated = true;
					}
					
					// Evaluate the meta title excerpt auto-generation
					if(!titlePopulated && !existingTargetTitle && jmap_metainfoAutoGenerateMetatitle) {
						var autoGeneratedTitleText = '';
						var titleSource = $(jmap_metainfoAutoGenerateMetatitleCssSelector, $.parseHTML(responseData)).text().trim().replace(/\s\s+/g, ' ');
						
						var titleSourceLength = titleSource.length;
						if(titleSourceLength > 0) {
							if(titleSourceLength > jmap_metainfoAutoGenerateMetatitleMaxLength) {
								autoGeneratedTitleText = titleSource.substr(0, jmap_metainfoAutoGenerateMetatitleMaxLength) + '...';
							} else {
								autoGeneratedTitleText = titleSource;
							}
							
							$(targetTitle).text( autoGeneratedTitleText ).val( autoGeneratedTitleText );
							titlePopulated = true;
						}
					}
					
					// STEP 3 - Description retrieval and reporting
					var description = responseDataWrappedSet.filter('meta[name=description]').attr('content') || '';
					description = description.trim();
					// Manage description validity, preserve existing values
					var existingTargetDesc = $(targetDesc).text();
					if(description && !existingTargetDesc && jmap_metainfoAutoGenerateMetadescription != 2) {
						$(targetDesc).text( description ).val( description );
						descPopulated = true;
					}
					
					// Evaluate the meta description excerpt auto-generation
					if(!descPopulated && !existingTargetDesc && jmap_metainfoAutoGenerateMetadescription) {
						var autoGeneratedDescriptionText = '';
						var descriptionSource = $(jmap_metainfoAutoGenerateMetadescriptionCssSelector, $.parseHTML(responseData)).text().trim().replace(/\s\s+/g, ' ');
						
						var descriptionSourceLength = descriptionSource.length;
						if(descriptionSourceLength > 0) {
							if(descriptionSourceLength > jmap_metainfoAutoGenerateMetadescriptionMaxLength) {
								autoGeneratedDescriptionText = descriptionSource.substr(0, jmap_metainfoAutoGenerateMetadescriptionMaxLength) + '...';
							} else {
								autoGeneratedDescriptionText = descriptionSource;
							}
							
							$(targetDesc).text( autoGeneratedDescriptionText ).val( autoGeneratedDescriptionText );
							descPopulated = true;
						}
					}
					
					// STEP 4 - Robots directive retrieval and selecting
					var robots = responseDataWrappedSet.filter('meta[name=robots]').attr('content') || '';
					robots = robots.trim();
					// Manage description validity, preserve existing values
					if(robots && !$(targetRobots).val()) {
						$(targetRobots).val( robots );
						robotsPopulated = true;
					}
					
					// STEP 5 - Social image retrieval and selecting
					if(jmap_metainfoAutopopulateSocialimageSelector) {
						var socialImage = responseDataWrappedSet.find(jmap_metainfoAutopopulateSocialimageSelector).attr('src') || 
										  responseDataWrappedSet.find(jmap_metainfoAutopopulateSocialimageSelector).attr('data-src') || 
										  '';
						socialImage = socialImage.trim().replace(/^\//, "");
						// Manage description validity, preserve existing values
						if(socialImage && !$(targetSocialImage).val()) {
							// Check for sub folder overlap
							var baseChunksLastChunk = jmap_baseURI.replace(/\/$/, "").split('/').pop();
							var imageChunksFirstChunk = socialImage.split('/')[0];
							if(baseChunksLastChunk == imageChunksFirstChunk) {
								socialImage = socialImage.replace(baseChunksLastChunk + '/', '');
							}
							
							$(targetSocialImage).val( socialImage ).attr('value', socialImage);
							
							// Refresh the image popover
							var imgPreview = new Image(200, 200);
							
							// Regex pattern for checking absolute URL
				            var pattern = /^http(s)?:\/\//i; 
				            
				            // Check if pattern is there in the string or not with .test() method 
				            if (pattern.test(socialImage)) { 
				            	imgPreview.src = socialImage;
				            } 
				            else { 
				            	imgPreview.src = jmap_baseURI + socialImage;
				            } 
							
							var containerPreview = $(targetSocialImage).prev('span.field-media-preview');
							try {
								containerPreview.popover('destroy');
							} catch(err) {}
							containerPreview.popover({content: imgPreview});
							
							socialImagePopulated = true;
						}
					}
					
					// Now trigger the change event for this row
					if(titlePopulated || descPopulated || robotsPopulated || socialImagePopulated) {
						$(targetTitle).trigger('keyup.metainfo');
						$(targetDesc).trigger('keyup.metainfo');
						// Finally trigger the save data button to persist
						var targetSaveBtn = $('button[data-action=savemeta]').get(index);
						$(targetSaveBtn).trigger('click.metainfo');
					}
				});
			});
		};
		
		/**
		 * Request a refresh of the row status
		 * 
		 * @access public
		 * @param Object parentRow
		 * @param Integer identifier
		 * @return Void
		 */
		this.refreshRowStatus = function(parentRow, identifier) {
			rowButtonStatus(parentRow);
			
			// Check if it's a deletion meta image after a button click in the media widget
			var titleValue = $('textarea.metatitle', parentRow).val();
			var descValue = $('textarea.metadesc', parentRow).val();
			var imageValue = $('input.mediaimagefield', parentRow).val();
			var robotsValue = $('select.robots_directive', parentRow).val();
			
			// If none of the values are available AKA empty record delete on server
			if(!titleValue && !descValue && !imageValue && !robotsValue) {
				// Retrive information to save
				var linkIdentifier = $('a[data-linkidentifier=' + identifier + ']').attr('href');
				
				// Now build the object to send to server endpoint
				var dataObject = {
					linkurl : linkIdentifier
				};
				
				// Now save to server side
				saveDataStatus('deleteMeta', dataObject);
			}
		};
		
		/**
		 * Request a refresh of the row status
		 * 
		 * @access public
		 * @return Void
		 */
		this.startMetaCount = function() {
			$('table.adminlist tbody textarea.metatitle').jmapCharacterCount({
	            limit: 70
	        });
			
			$('table.adminlist tbody textarea.metadesc').jmapCharacterCount({
	            limit: 160
	        });
			
			// Enable Bootstrap tooltips
			$('span.labelmetainfo').tooltip({trigger:'hover', placement:'top', html : 1});
			$('button.jmap-chatgpt-metagenerator').tooltip({trigger:'hover', placement:'top', html : 1});
		};
		
		/**
		 * Function dummy constructor
		 * 
		 * @access private
		 * @param String
		 *            contextSelector
		 * @method <<IIFE>>
		 * @return Void
		 */
		(function __construct() {
			// Search for each row if at least one of the 3 values is specified and only in that case enable buttons
			rowButtonStatus();
			
			// Extend jQuery with plugins
			addTextareaLengthvalidation();
			
			// Manage the async font loading for the update controls
			if(typeof(WebFont) !== 'undefined') {
				WebFont.load({
					google: {
						families: ['Roboto:400']
					}
				});
			}
			
			// Fix for Joomla 3.5 modals
			$('td.metaimage a.button-select').on('click', function(jqEvent) {
				$('button[data-dismiss=modal]').removeAttr('disabled');
			})
			$('td.metaimage input.field-media-input.mediaimagefield').removeAttr('readonly');
			
			$('#toolbar-database button, #toolbar-save button').removeAttr('onclick');
			 
			// Add UI events
			addListeners.call(this, true);
		}).call(this);
	}

	// On DOM Ready
	$(function() {
		window.JMapMetainfo = new Metainfo();
	});
})(jQuery);