/* global Choices, wpf, wpforms_builder */

'use strict';

var WPForms = window.WPForms || {};

WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};

WPForms.Admin.Builder.Notifications = WPForms.Admin.Builder.Notifications || ( function( document, window, $ ) {

	/**
	 * Elements holder.
	 *
	 * @since 1.7.7
	 *
	 * @type {object}
	 */
	let el = {};

	/**
	 * ChoicesJS config.
	 *
	 * @since 1.7.7
	 *
	 * @type {object}
	 */
	const choicesJSConfig = {
		removeItemButton: true,
		shouldSort: false,
	};

	/**
	 * ChoicesJS config for File Upload attachment field.
	 *
	 * @since 1.7.8
	 *
	 * @type {object}
	 */
	let choicesJSConfigFileUpload;

	/**
	 * Advanced Notification sections.
	 *
	 * @since 1.7.8
	 *
	 * @type {Array}
	 */
	const advancedNotificationSections = [
		{ // File Upload Attachments.
			toggle: {
				className: '.notifications_enable_file_upload_attachment_toggle',
				actionElements: [ 'file_upload_attachment_fields' ],
			},
			choicesJS: {
				fieldName: 'file_upload_attachment_fields',
				choices: 'fileUpload',
			},
		},
		{ // Entry CSV Attachment.
			toggle: {
				className: '.notifications_enable_entry_csv_attachment_toggle',
				actionElements: [
					'entry_csv_attachment_entry_information',
					'entry_csv_attachment_file_name',
				],
			},
			choicesJS: {
				fieldName: 'entry_csv_attachment_entry_information',
				choices: 'entryInformation',
			},
		},
	];

	/**
	 * Public functions and properties.
	 *
	 * @since 1.7.7
	 *
	 * @type {object}
	 */
	const app = {

		/**
		 * Init Advanced Notifications section.
		 *
		 * @since 1.7.7
		 */
		init: function() {

			choicesJSConfigFileUpload = Object.assign( {}, choicesJSConfig );
			choicesJSConfigFileUpload.noChoicesText = wpforms_builder.notifications_file_upload.no_choices_text;

			app.setup();
			app.bindEvents();

			$( app.ready );
		},

		/**
		 * Document ready.
		 *
		 * @since 1.7.7
		 */
		ready: function() {

			if ( typeof window.Choices !== 'function' ) {
				return;
			}

			app.maybeSaveFormState();
		},

		/**
		 * Setup. Prepare some variables.
		 *
		 * @since 1.7.7
		 */
		setup: function() {

			// Cache DOM elements.
			el = {
				$builder: $( '#wpforms-builder' ),
			};
		},

		/**
		 * Initialized the Advanced section fields.
		 *
		 * @since 1.7.8
		 *
		 * @param {object}  $block             jQuery element of the block.
		 * @param {boolean} isDynamicallyAdded Whether this block is dynamically added or not.
		 * @param {string}  originalId         Original notification block id if notification is cloned.
		 */
		initBlock: function( $block, isDynamicallyAdded = false, originalId = '' ) {

			const blockID = $block.data( 'blockId' );

			if ( $block.data( 'blockType' ) !== 'notification' || ! blockID ) {
				return;
			}

			advancedNotificationSections.forEach( function( section ) {

				app.initToggle(
					$block,
					blockID,
					section.toggle.className,
					section.toggle.actionElements
				);

				const $choicesJSField = $block.find( `.${section.choicesJS.fieldName}` ).first();

				if ( $choicesJSField.length <= 0 ) {
					return;
				}

				let choices;
				let choicesConfig = choicesJSConfig;

				if ( section.choicesJS.choices === 'fileUpload' ) {
					choices = app.choicesJSHelperMethods.fileUploadFields.getAllChoices();

					if ( choices[0].choices.length === 0 ) {
						choicesConfig = choicesJSConfigFileUpload;
					}
				} else {
					choices = app.choicesJSHelperMethods.entryInformation.getAllChoices();
				}

				// Handle initial ChoicesJS field.
				if ( isDynamicallyAdded ) {

					/*
					 We should clear existing choices for added notification block
					 But not on cloned blocks.
					 */
					const shouldClearExistingChoices = ! originalId;

					app.initDynamicallyAddedChoicesJS(
						$block,
						section.choicesJS.fieldName,
						shouldClearExistingChoices
					);

					app.setDynamicallyAddedEntryInformationFileNameFieldValue( $block, originalId );
				}

				app.initChoicesJS( $choicesJSField, choices, choicesConfig );

				if ( section.choicesJS.choices === 'fileUpload' ) {

					// Initially compute the File Upload Attachment Size.
					app.computeForFileUploadAttachmentSize( $choicesJSField );

					$choicesJSField.on( 'change', app.fileUploadFieldChange );
				}
			} );
		},

		/**
		 * Initialize toggle.
		 *
		 * @since 1.7.8
		 *
		 * @param {Object} $block         jQuery element of the block.
		 * @param {number} blockID        The block ID.
		 * @param {string} toggleElClass  Class name of the toggle element.
		 * @param {Array}  actionElements Elements that are controlled by the toggle.
		 *
		 * @return {boolean} Whether or not the toggle has been initialized.
		 */
		initToggle( $block, blockID, toggleElClass, actionElements ) {
			const $toggleEl = $block.find( toggleElClass ).first();

			if ( $toggleEl.length <= 0 ) {
				return false;
			}

			$toggleEl.on( 'change', function() {
				const $this = $( this ),
					choices = app.choicesJSHelperMethods.getFormFields( [ 'file-upload' ] );

				$this.trigger( 'wpformsNotificationsToggleConditionalChange', [ choices ] );
			} );

			return app.setupToggleConditional( $toggleEl, blockID, actionElements );
		},

		/**
		 * Setup the conditional for the toggle and its action elements.
		 *
		 * @since 1.7.8
		 *
		 * @param {object} $toggleEl      jQuery element of the toggle.
		 * @param {number} blockID        The block ID.
		 * @param {Array}  actionElements Elements that are controlled by the toggle.
		 *
		 * @returns {boolean} Returns `false` if there are no action elements found. Otherwise, returns `true`.
		 */
		setupToggleConditional: function( $toggleEl, blockID, actionElements ) {

			const actionElementIds = actionElements.map( function( element ) {
				return `#wpforms-panel-field-notifications-${blockID}-${element}-wrap`;
			} );

			if ( actionElementIds.length <= 0 ) {
				return false;
			}

			const actionElement = actionElementIds.join( ',' );

			$toggleEl.conditions( [
				{
					conditions: {
						element:   $toggleEl,
						type:      'checked',
						operator:  'is',
						condition: '1',
					},
					actions: {
						if: {
							element: actionElement,
							action: 'show',
						},
						else: {
							element: actionElement,
							action:  'hide',
						},
					},
					effect: 'appear',
				},
			] );

			return true;
		},

		/**
		 * Initialize ChoicesJS in a given select field.
		 *
		 * @since 1.7.7
		 * @since 1.7.8 Added `choices` and `choicesConfig` parameters.
		 *
		 * @param {object} $selectEl     jQuery element of the select field.
		 * @param {Array}  choices       Array containing the choices for the ChoicesJS instance.
		 * @param {object} choicesConfig ChoicesJS config.
		 *
		 * @returns {false|Choices} ChoicesJS instance.
		 */
		initChoicesJS: function( $selectEl, choices, choicesConfig ) {

			const fieldName = $selectEl.attr( 'name' );

			if ( ! fieldName ) {
				return false;
			}

			// Original val.
			const originalVal = $selectEl.val(),
				choiceJSInstance = $selectEl.data( 'choicesjs' );

			if ( typeof choiceJSInstance !== 'undefined' ) {
				choiceJSInstance.destroy();
			}

			const choicesJS = new Choices( $selectEl[0], choicesConfig );

			// Init and cache the ChoicesJS init.
			$selectEl.data( 'choicesjs', choicesJS );

			$selectEl
				.on( 'change', app.changeChoicesJS );

			app.choicesJSHelperMethods.populateInstance( choicesJS, choices, originalVal );

			choicesJS.passedElement.element.addEventListener( 'addItem', function( event ) {
				el.$builder.trigger( 'wpformsNotificationFieldAdded', [ event.detail.value, event.target ] );
			} );

			choicesJS.passedElement.element.addEventListener( 'removeItem', function( event ) {
				el.$builder.trigger( 'wpformsNotificationFieldRemoved', [ event.detail.value, event.target ] );
			} );

			return choicesJS;
		},

		/**
		 * ChoicesJS field change event handler.
		 *
		 * @since 1.7.8
		 */
		changeChoicesJS: function() {

			const $this = $( this );
			const currentVal = $this.data( 'choicesjs' ).getValue();
			let fieldName = $this.attr( 'name' );

			if ( ! fieldName || ! currentVal ) {
				return;
			}

			// Find the closest hidden field designated to this field.
			const hiddenFieldName = fieldName + '[hidden]',
				hiddenField = $this.closest( '.wpforms-panel-field' ).find( `input[name="${hiddenFieldName}"]` );

			if ( hiddenField.length <= 0 ) {
				return;
			}

			const newVal = [];

			for ( let i = 0; i < currentVal.length; i++ ) {
				newVal.push( currentVal[ i ].value );
			}

			hiddenField.val( JSON.stringify( newVal ) );
		},

		/**
		 * Change event handler for "File Upload Fields" select/ChoicesJS field.
		 *
		 * @since 1.7.8
		 */
		fileUploadFieldChange: function() {

			app.computeForFileUploadAttachmentSize( $( this ) );
		},

		/**
		 * Compute and display the file upload size in MB.
		 *
		 * @since 1.7.8
		 *
		 * @param {object} $choicesJSField jQuery element of the ChoicesJS field.
		 */
		computeForFileUploadAttachmentSize: function( $choicesJSField ) {

			const currentVal = $choicesJSField.data( 'choicesjs' ).getValue();
			const $fileSizeDom = $choicesJSField.parents( '.wpforms-panel-field' ).find( '.notifications-file-upload-attachment-size' );

			if ( $fileSizeDom.length <= 0 ) {
				return;
			}

			if ( currentVal.length <= 0 ) {
				$fileSizeDom.text( 0 );
				return;
			}

			const defaultMaxSize = Number( wpforms_builder.notifications_file_upload.wp_max_upload_size );
			let fileUploadSize = 0;

			for ( let counter = 0; counter < currentVal.length; counter++ ) {
				const field = wpf.getField( currentVal[ counter ].value );

				if ( field.type !== 'file-upload' ) {
					continue;
				}

				const maxSize = app.utils.convertToNumber( field.max_size, defaultMaxSize );
				const maxFileNumber = app.utils.convertToNumber( field.max_file_number, 1 );

				fileUploadSize += maxSize * maxFileNumber;
			}

			$fileSizeDom.text( +wpf.numberFormat( fileUploadSize, 2, '.', ',' ).replace( ',', '' ) );
		},

		/**
		 * Initialized ChoicesJS in Entry Information field in the newly added notifications block.
		 *
		 * @since 1.7.8
		 *
		 * @param {object}  $block                     jQuery element of the newly added notifications block.
		 * @param {string}  fieldName                  Field name.
		 * @param {boolean} shouldClearExistingChoices Whether to clear existing selected choices or not.
		 */
		initDynamicallyAddedChoicesJS: function( $block, fieldName, shouldClearExistingChoices ) {

			const blockID = $block.data( 'blockId' );

			// Find the ChoicesJS Field wrapper.
			const $divWrapper = $block.find( `#wpforms-panel-field-notifications-${blockID}-${fieldName}-wrap` );

			if ( $divWrapper.length <= 0 ) {
				return;
			}

			// Find the ChoicesJS Wrapper inside the newly added notification.
			const $choicesWrapper = $divWrapper.find( '.choices' );

			if ( $choicesWrapper.length <= 0 ) {
				return;
			}

			const $choicesJSField = $choicesWrapper.find( `.${fieldName}` ).first();

			if ( $choicesJSField.length <= 0 ) {
				return;
			}

			// Remove the ChoicesJS artifact.
			$choicesJSField
				.removeClass( 'choices__input' )
				.removeAttr( 'hidden' )
				.removeAttr( 'data-choice' )
				.removeData( 'choice' )
				.prependTo( $divWrapper.first() );

			$divWrapper.find( 'label:first' ).prependTo( $divWrapper.first() );

			// Delete the ChoicesJS wrapper DOM.
			$choicesWrapper.first().remove();

			// Make sure nothing is pre-selected for.
			if ( shouldClearExistingChoices ) {
				$choicesJSField.val( [] );
			}
		},

		/**
		 * Bind events.
		 *
		 * @since 1.7.7
		 */
		bindEvents: function() {

			el.$builder
				.on( 'wpformsSettingsBlockAdded', app.notificationBlockAdded )
				.on( 'wpformsSettingsBlockCloned', app.notificationsBlockCloned )
				.on( 'wpformsPanelSwitch', app.panelSwitch )
				.on( 'wpformsPanelSectionSwitch', app.panelSectionSwitch );
		},

		/**
		 * Resetting fields when we add a new webhook.
		 *
		 * @since 1.7.8
		 *
		 * @param {object} event  Event object.
		 * @param {object} $block New Webhook block.
		 */
		notificationBlockAdded: function( event, $block ) {

			app.initBlock( $block, true );
		},

		/**
		 * Insert the default value in File Name field for dynamically added
		 * notification block.
		 *
		 * @since 1.7.7
		 * @since 1.7.8 Replaced the second parameter from preselected file name to `originalId`.
		 *
		 * @param {object} $block     jQuery element of the newly added notifications block.
		 * @param {string} originalId Original notification block id if notification is cloned.
		 */
		setDynamicallyAddedEntryInformationFileNameFieldValue: function( $block, originalId ) {

			const $entryInformationFileNameField = $block.find( '.entry_csv_attachment_file_name' );

			if ( $entryInformationFileNameField.length <= 0 ) {
				return;
			}

			const preSelectedFileName = originalId ? $( `#wpforms-panel-field-notifications-${originalId}-entry_csv_attachment_file_name` ).val() : '';

			$entryInformationFileNameField.val( preSelectedFileName.length === 0 ? wpforms_builder.entry_information.default_file_name : preSelectedFileName );
		},

		/**
		 * Triggered when a notification block was cloned.
		 *
		 * @since 1.7.7
		 *
		 * @param {Event}  ev         Event object.
		 * @param {object} $clone     jQuery element cloned.
		 * @param {string} originalId Original notification ID.
		 */
		notificationsBlockCloned: function( ev, $clone, originalId ) {

			app.initBlock( $clone, true, originalId );
		},

		/**
		 * Event handler for panel switch.
		 *
		 * @since 1.7.8
		 *
		 * @param {object} event Event object.
		 * @param {string} panel Active panel.
		 */
		panelSwitch: function( event, panel ) {

			if ( panel !== 'settings' ) {
				return;
			}

			// Find the active section.
			const activeSection = $( '#wpforms-panel-settings .wpforms-panel-sidebar' ).find( '.wpforms-panel-sidebar-section.active' ).data( 'section' );

			if ( activeSection !== 'notifications' ) {
				return;
			}

			app.loopAllNotificationsBlock( function( $block, blockID ) {
				app.initBlock( $block );
			} );
		},

		/**
		 * Loop through all notifications block and invoke the passed callback function.
		 *
		 * @since 1.7.8
		 *
		 * @param {Function} callback Callback function.
		 */
		loopAllNotificationsBlock: function( callback ) {

			$( '.wpforms-notification.wpforms-builder-settings-block' ).each( function( index, block ) {

				const $block = $( block );
				const blockID = $block.data( 'blockId' );

				if ( $block.data( 'blockType' ) !== 'notification' || ! blockID ) {
					return;
				}

				callback( $block, blockID );
			} );

			// Re-save the form state since DOM has been changed.
			app.maybeSaveFormState();
		},

		/**
		 * Event handler for panel section switch.
		 *
		 * @since 1.7.8
		 *
		 * @param {object} event   Event object.
		 * @param {string} section Active section.
		 */
		panelSectionSwitch: function( event, section ) {

			if ( section !== 'notifications' ) {
				return;
			}

			app.loopAllNotificationsBlock( function( $block, blockID ) {
				app.initBlock( $block );
			} );
		},

		/**
		 * Save the form state if it was changed during the initialization process.
		 *
		 * @since 1.7.7
		 */
		maybeSaveFormState: function() {

			const currentState = wpf.getFormState( '#wpforms-builder-form' );

			// If some elements were changed (e.g. ChoiceJS instance was pre-populated),
			// then the whole form state was changed as well.
			// That's why we need to re-save it.
			if ( wpf.savedState !== currentState ) {
				wpf.savedState = currentState;
			}
		},

		/**
		 * Helper methods for ChoicesJS.
		 *
		 * @since 1.7.7
		 */
		choicesJSHelperMethods: {

			/**
			 * Choices for "File Upload Fields" field.
			 *
			 * @since 1.7.8
			 */
			fileUploadFields: {

				/**
				 * Get all choices.
				 *
				 * @since 1.7.8
				 *
				 * @returns {Array} Array of all the choices.
				 */
				getAllChoices: function() {

					return [
						{
							label: 'hidden',
							choices: app.choicesJSHelperMethods.getFormFields( [ 'file-upload' ] ),
						},
					];
				},
			},

			/**
			 * Choices for "Entry Information" field.
			 *
			 * @since 1.7.8
			 */
			entryInformation: {

				/**
				 * Get all choices.
				 *
				 * @since 1.7.8
				 *
				 * @returns {Array} Array of all the choices.
				 */
				getAllChoices: function() {

					return [
						{
							label: 'hidden',
							choices: [
								{
									value: 'all_fields',
									label: wpforms_builder.entry_information.localized.all_fields,
								},
							],
						},
						{
							label: wpforms_builder.fields_available,
							choices: app.choicesJSHelperMethods.getFormFields( false, wpforms_builder.entry_information.excluded_field_types ),
						},
						{
							label: wpforms_builder.other,
							choices: app.choicesJSHelperMethods.entryInformation.getOtherChoices(),
						},
					];
				},

				/**
				 * Get "Other" options.
				 *
				 * @since 1.7.8
				 *
				 * @returns {*[]} Returns an array of objects containing the label and value (field key).
				 */
				getOtherChoices: function() {

					let otherFields = [];

					// Get "Others".
					for ( const smartTagKey in wpforms_builder.smart_tags ) {

						if ( wpforms_builder.entry_information.excluded_tags.includes( smartTagKey ) ) {
							continue;
						}

						// Replace the value if necessary.
						let value = Object.hasOwn( wpforms_builder.entry_information.replacement_tags, smartTagKey ) ? wpforms_builder.entry_information.replacement_tags[ smartTagKey ] : smartTagKey;

						otherFields.push( {
							label: wpf.encodeHTMLEntities( wpforms_builder.smart_tags[ smartTagKey ] ),
							value: wpf.sanitizeHTML( value ),
						} );
					}

					return otherFields;
				},
			},

			/**
			 * Get current form fields.
			 *
			 * @since 1.7.8
			 *
			 * @param {Array|false} allowed Field types to return. Pass `false` to return all fields.
			 * @param {Array}       exclude Field types to exclude.
			 *
			 * @return {Array} Array containing fields in `object` with `label` and `value` properties.
			 */
			getFormFields( allowed, exclude = [] ) {
				const availableFields = [];

				const fields = wpf.getFields( allowed, true, true );

				if ( ! fields ) {
					return [];
				}

				for ( const fieldKey in fields ) {
					const field = fields[ fieldKey ];

					if ( field.label === undefined || app.choicesJSHelperMethods.isFieldExcluded( field, exclude ) ) {
						continue;
					}

					const hasRestrictions = app.choicesJSHelperMethods.fieldHasRestrictions( field.id );
					let label = wpf.encodeHTMLEntities( app.choicesJSHelperMethods.getFieldLabel( field ) );

					if ( hasRestrictions ) {
						label += '<span class="wpfroms-notifications-restrictions-enabled">' + wpforms_builder.notifications_file_upload.restrictions_enabled + '</span>';
					}

					availableFields.push( {
						label,
						value: wpf.sanitizeHTML( field.id.toString() ),
						disabled: hasRestrictions,
					} );
				}

				return availableFields;
			},

			/**
			 * Check whether the given `field` is excluded based on the passed `exclude`.
			 *
			 * @since 1.7.8
			 *
			 * @param {object} field   Field to check.
			 * @param {Array}  exclude Array of fields that should be excluded.
			 *
			 * @returns {boolean} Whether the given `field` is excluded.
			 */
			isFieldExcluded: function( field, exclude ) {

				return Array.isArray( exclude ) && exclude.length > 0 && exclude.includes( field.type );
			},

			/**
			 * Check if the field is restricted.
			 *
			 * @since 1.9.4
			 *
			 * @param {number} fieldId The ID of the field.
			 *
			 * @return {boolean} True if the field is restricted, false otherwise.
			 */
			fieldHasRestrictions( fieldId ) {
				return Boolean( $( `#wpforms-field-option-${ fieldId }-is_restricted` ).prop( 'checked' ) );
			},

			/**
			 * Returns the label of a field.
			 *
			 * If the field doesn't have a label, it will use `wpforms_builder.empty_label_alternative_text`
			 * instead.
			 *
			 * @since 1.7.8
			 *
			 * @param {object} field Field object.
			 *
			 * @returns {string} Label of the field.
			 */
			getFieldLabel: function( field ) {

				return field.label.length === 0 ? `${wpforms_builder.empty_label_alternative_text}${field.id}` : field.label;
			},

			/**
			 * Populate ChoiceJS instance with values with optional pre-selected ones.
			 *
			 * @since 1.7.7
			 *
			 * @param {Choices} choicesJS   ChoicesJS instance.
			 * @param {Array}   choices     Array containing the choices for the ChoicesJS instance.
			 * @param {Array}   preSelected Array of pre-selected choices.
			 */
			populateInstance: function( choicesJS, choices, preSelected = [] ) {

				if ( ! choicesJS ) {
					return;
				}

				choicesJS.clearStore();

				choicesJS.setChoices( choices );

				if ( ! Array.isArray( preSelected ) ) {
					return;
				}

				preSelected.forEach( function( item ) {
					choicesJS.setChoiceByValue( item );
				} );
			},
		},

		/**
		 * Other utils.
		 *
		 * @since 1.7.8
		 */
		utils: {

			/**
			 * Converts a given value to a number.
			 *
			 * If the converted value is less than or equal to 0, return the default value instead.
			 *
			 * @since 1.7.8
			 *
			 * @param {any}    value        Value to be converted.
			 * @param {number} defaultValue Default value.
			 *
			 * @returns {number} The value converted to a number or the default value.
			 */
			convertToNumber: function( value, defaultValue ) {

				const convertValue = Number( value );

				return ( convertValue <= 0 ) ? defaultValue : convertValue;
			},
		},
	};

	// Provide access to public functions/properties.
	return app;

}( document, window, jQuery ) );

// Initialize.
WPForms.Admin.Builder.Notifications.init();
