/* global wpforms_builder, wpf, tinymce, quicktags */

'use strict';

/**
 * WPForms Content Field builder functions.
 *
 * @since 1.7.8
 */
var WPForms = window.WPForms || {};

WPForms.Admin                      = WPForms.Admin || {};
WPForms.Admin.Builder              = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.ContentField = WPForms.Admin.Builder.ContentField || ( function( document, window, $ ) {

	let app = {

		/**
		 * Duplicated field id helper variable.
		 *
		 * @since 1.7.8
		 *
		 * @type {string|undefined}
		 */
		duplicatedFieldContent: undefined,

		/**
		 * CSS class for the div ending field content preview.
		 *
		 * @since 1.7.8
		 *
		 * @type {string}
		 */
		contentPreviewEndClass: 'wpforms-field-content-preview-end',

		/**
		 * The id of the layout field where user just added some field.
		 *
		 * @since 1.7.8
		 *
		 * @type {bool|int}
		 */
		updatedLayoutFieldId: false,

		/**
		 * Stores tinymce.PluginManager instance.
		 *
		 * @since 1.7.8
		 *
		 * @type {object|tinymce.PluginManager}
		 */
		_pluginManager: {},

		/**
		 * Textarea ID.
		 *
		 * @since 1.7.8
		 *
		 * @param {int} id Field ID.
		 *
		 * @returns {string} Textarea ID string without # symbol.
		 */
		textareaId: function( id ) {

			return `wpforms-field-option-${id}-content`;
		},

		/**
		 * Content wrap ID.
		 *
		 * @since 1.7.8
		 *
		 * @param {int} id Field ID.
		 *
		 * @returns {string} Content wrap ID.
		 */
		contentWrap: function( id ) {

			return `wp-wpforms-field-option-${id}-content-wrap`;
		},

		/**
		 * Start the engine.
		 *
		 * @since 1.7.8
		 */
		init: function() {

			app.bindInitInstanceCallback();
			$( app.ready );
		},

		/**
		 * Initialized once the DOM is fully loaded.
		 *
		 * @since 1.7.8
		 */
		ready: function() {
			$( '#wpforms-builder' )
				.on( 'wpformsFieldAdd', app.onWpformsFieldAdd )
				.on( 'wpformsFieldMove', app.onWpformsFieldMove )
				.on( 'wpformsBeforeFieldDuplicate', app.onWpformsBeforeFieldDuplicate )
				.on( 'wpformsFieldDuplicated', app.onWpformsFieldDuplicated )
				.on( 'wpformsBeforeSave', app.onWpformsBeforeSave )
				.on( 'click', '.wpforms-content-button-update-preview.update-preview', app.updatePreview )
				.on( 'click', '.wpforms-content-button-expand-editor.expand-editor', app.expandEditor )
				.on( 'click', '.wpforms-panel-sidebar-toggle:not(.wpforms-panel-sidebar-closed)', app.collapseExpandedEditor )
				.on( 'wpformsLayoutAfterReceiveFieldToColumn', app.onWpformsLayoutAfterReceiveFieldToColumn )
				.on( 'wpformsLayoutAfterUpdateColumnsData', app.layoutChanged )
				.on( 'click', '.wpforms-expandable-editor .insert-media', app.onInsertMediaButtonClicked )
				.on( 'click', '.wpforms-expandable-editor .mce-toolbar button, .wpforms-expandable-editor .quicktags-toolbar input', app.onContentUpdated )
				.on( 'input', '.wpforms-expandable-editor .wp-editor-area', app.onContentUpdated )
				.on( 'click', '.wpforms-expandable-editor .wp-switch-editor.switch-html', app.setTextareaVisible )
				.on( 'click', '.wpforms-panel-content-wrap .wpforms-field', app.hideImageToolbar )
				.on( 'wpformsBuilderConfirmationsReady', app.setPluginManager );
		},

		/**
		 * Reset editor when field is added first time or duplicated.
		 *
		 * @since 1.7.8
		 *
		 * @param {object} event Event object.
		 * @param {int}    id    Editor ID.
		 * @param {string} type  Field type.
		 */
		onWpformsFieldAdd: function( event, id, type ) {

			app.updateContentFieldInColumns();
			app.hasEditor( type ) && app.resetEditor( id, app.duplicatedFieldContent );
		},

		/**
		 * When someone puts any field in column, change the flag to mark the action started.
		 *
		 * @since 1.7.8
		 *
		 * @param {Event}  event wpformsLayoutAfterReceiveFieldToColumn event.
		 * @param {object} data  Data object.
		 */
		onWpformsLayoutAfterReceiveFieldToColumn: function( event, data ) {

			let $layoutField = data.column.closest( '.wpforms-field.wpforms-field-layout' );

			if ( $layoutField.length > 0 ) {
				app.updatedLayoutFieldId = $layoutField.data( 'field-id' );
			}
		},

		/**
		 * When a layout field is updated (preset is changed, inner fields are added/moved, etc.) - reset content fields inside.
		 *
		 * @since 1.7.8
		 *
		 * @param {Event}  event jQuery.Event object.
		 * @param {object} data  Layout field data.
		 */
		layoutChanged: function( event, data ) {

			// If the reason of the layout change was the duplication of content field inside layout, do not proceed.
			if ( typeof app.duplicatedFieldContent !== 'undefined' ) {
				return;
			}

			if ( data.fieldId > 0 ) {
				app.resetFieldsInLayout( data.fieldId );
			}
		},

		/**
		 * If adding field to layout's column has been triggered, reset all editor fields in the same layout.
		 *
		 * Without this, editor fields gets empty.
		 *
		 * @since 1.7.8
		 */
		updateContentFieldInColumns: function() {

			if ( app.updatedLayoutFieldId ) {
				app.resetFieldsInLayout( app.updatedLayoutFieldId );
				app.updatedLayoutFieldId = false;
			}
		},

		/**
		 * Reset editor on field move.
		 *
		 * @since 1.7.8
		 *
		 * @param {object} event Event object.
		 * @param {object} ui    UI object.
		 */
		onWpformsFieldMove: function( event, ui ) {

			if ( ui.item.data( 'field-type' ) === 'layout' ) {
				app.resetFieldsInLayout( ui.item.data( 'field-id' ) );
				return;
			}

			if ( ! app.hasEditor( ui.item.data( 'field-type' ) ) ) {
				return;
			}

			let id           = ui.item.data( 'field-id' ),
				currentValue = app.getEditorContent( id );

			app.resetEditor( id, currentValue );
			app.updateContentFieldInColumns();
		},

		/**
		 * Traverse all content fields and reset only those which are inside layout field with given ID.
		 *
		 * @param {int} layoutFieldId Layout field ID.
		 */
		resetFieldsInLayout: function( layoutFieldId ) {

			$( '.wpforms-field-content.wpforms-field' ).each(
				function() {
					let $fieldPreview = $( this ),
						editorID      = $fieldPreview.data( 'field-id' );

					if ( WPForms.Admin.Builder.FieldLayout.columnsHasFieldID( layoutFieldId, editorID ) ) {
						app.resetEditor( editorID, app.getEditorContent( editorID ) );
					}
				}
			);
		},

		/**
		 * Store temporary field value in variable duplicatedFieldContent before field has been moved.
		 *
		 * @since 1.7.8
		 *
		 * @param {object} event  Event object.
		 * @param {int}    id     ID of the field which will be duplicated.
		 * @param {jQuery} $field Field object.
		 */
		onWpformsBeforeFieldDuplicate: function( event, id, $field ) {

			if ( $field.data( 'field-type' ) !== 'content' ) {
				return;
			}

			let $settings = $( `.wpforms-field-has-tinymce[data-field-id=${id}]` );

			app.renderPreview( $settings, id );
			app.duplicatedFieldContent = $field.find( '.wpforms-field-content-preview' ).html().replace( `<div class="${app.contentPreviewEndClass}"></div>`, '' );
		},

		/**
		 * Reset duplicatedFieldContent variable when the field duplication is done.
		 *
		 * @since 1.7.8
		 *
		 * @param {object} event      Event object.
		 * @param {int}    id         ID of the field which will be duplicated.
		 * @param {jQuery} $field     Field object.
		 * @param {int}    newFieldId ID of newly created field.
		 * @param {jQuery} $newField  New field object.
		 */
		onWpformsFieldDuplicated: function( event, id, $field, newFieldId, $newField ) {

			app.duplicatedFieldContent = undefined;

			if ( $field.data( 'field-type' ) === 'layout' ) {
				app.resetFieldsInLayout( id );
				app.resetFieldsInLayout( newFieldId );
			}
		},

		/**
		 * Callback triggered before form is going to be saved.
		 *
		 * @since 1.7.8
		 */
		onWpformsBeforeSave: function() {

			// Sanitize each textarea before saving the value.
			$( '.wpforms-field-has-tinymce textarea.wp-editor-area' ).each(
				function() {
					$( this ).val( wpf.sanitizeHTML( $( this ).val(), wpforms_builder.content_field.allowed_html ) );
				}
			);

			// Render preview of each content field.
			$( '.wpforms-field-has-tinymce' ).each(
				function() {
					app.renderPreview( null, $( this ).data( 'field-id' ) );
				}
			);
		},

		/**
		 * Callback for update preview on button click.
		 *
		 * @since 1.7.8
		 *
		 * @param {object} event Event object.
		 */
		updatePreview: function( event ) {

			event.preventDefault();

			app.renderPreview( $( this ).closest( '.wpforms-field-has-tinymce' ) );
		},

		/**
		 * Re-render field preview.
		 *
		 * @since 1.7.8
		 *
		 * @param {jQuery|null} $settings Content field settings element.
		 * @param {int|null}    fieldId   Field ID.
		 */
		renderPreview: function( $settings, fieldId = null ) {

			if ( typeof $settings !== 'object' && typeof fieldId !== 'number' ) {
				console.log( 'Cannot update preview. ContentField.renderPreview requires valid $settings object or valid fieldId' );
				return;
			}

			let id       = fieldId > 0 ? fieldId : $settings.data( 'field-id' ),
				$preview = $( `#wpforms-field-${id}` ).find( '.wpforms-field-content-preview' ),
				value    = app.parseShortcode( wpf.sanitizeHTML( app.getContentFromActiveView( id ), wpforms_builder.content_field.allowed_html ) );

			$preview.html( `${value}<div class="${app.contentPreviewEndClass}"></div>` );
		},

		/**
		 * Get content depending on which view is active (HTML or WYSIWYG).
		 *
		 * @since 1.7.8
		 *
		 * @param {int} id Field ID.
		 *
		 * @returns {string} Textarea or editor content.
		 */
		getContentFromActiveView: function( id ) {

			if ( $( `#${app.contentWrap( id )}` ).hasClass( 'html-active' ) ) {
				return wpf.wpautop( $( `#${app.textareaId( id )}` ).val() );
			}

			return app.getEditorContent( id );
		},

		/**
		 * Get content from editor.
		 *
		 * Tries to get value from editor. In cases when it is not loaded yet, retrieves value from textarea.
		 *
		 * @since 1.7.8
		 *
		 * @param {int} id The field ID.
		 *
		 * @returns {string} Editors content.
		 */
		getEditorContent: function( id ) {

			let editor = tinymce.get( app.textareaId( id ) );

			if ( editor ) {
				return editor.getContent();
			}

			return $( `#${app.textareaId( id )}` ).val();
		},

		/**
		 * Expand and collapse editor.
		 *
		 * @since 1.7.8
		 *
		 * @param {object} event Event object.
		 */
		expandEditor: function( event ) {

			event.preventDefault();

			let className           = 'wpforms-content-editor-expanded',
				$this               = $( this ),
				$panelSidebar       = $this.closest( '.wpforms-panel-sidebar' ),
				$tabContent         = $this.closest( '.wpforms-field-options.wpforms-tab-content' ),
				$fieldOptionContent = $this.closest( '.wpforms-field-option.wpforms-field-has-tinymce' ),
				$actionButtons      = $this.closest( '.wpforms-field-content-action-buttons' ),
				$sidebarWrapper     = $this.closest( '.wpforms-expandable-editor' ),
				$editorClear        = $sidebarWrapper.next( '.wpforms-expandable-editor-clear' ),
				editorHeight        = $sidebarWrapper.outerHeight( true ) + 20;

			$this.toggleClass( className ); // Add/remove class wpforms-content-editor-expanded to button.

			// Make action button wrapper same width as sidebar width.
			if ( $this.hasClass( className ) ) {
				$actionButtons.width( $sidebarWrapper.width() || 360 );
				$editorClear.css( 'margin-bottom', `${ editorHeight }px` );
			} else {
				$editorClear.css( 'margin-bottom', 0 );
			}

			// Add/remove class wpforms-content-editor-expanded to editor/sidebar elements which needs different styling when expanded.
			[ $panelSidebar, $tabContent, $fieldOptionContent ].forEach(
				function( element ) {

					if ( $this.hasClass( className ) ) {
						element.addClass( className );
						$this.find( 'span' ).text( wpforms_builder.content_field.collapse );

						return;
					}
					$( `.${className}` ).removeClass( className );
					$( 'button.expand-editor' ).each(
						function() {
							$( this ).find( 'span' ).text( wpforms_builder.content_field.expand );
						}
					);
				}
			);
		},

		/**
		 * Collapse editor when sidebar is closed.
		 *
		 * @since 1.8.8
		 */
		collapseExpandedEditor() {
			$( '.wpforms-content-button-expand-editor.wpforms-content-editor-expanded' ).trigger( 'click' );
		},

		/**
		 * Set plugin manager instance.
		 *
		 * Create fake tinymce instance and use it to instantiate tinymce.PluginManager.
		 *
		 * @since 1.7.8
		 */
		setPluginManager: function() {

			if ( $( '#wpforms-builder' ).length > 0 && typeof tinymce !== 'undefined' ) {
				let $fakeTextarea = $( document.createElement( 'textarea' ) ),
					textareaId    = 'wpforms-content-field-fake-div';

				$fakeTextarea.attr( 'id', textareaId ).css( 'display', 'none' ).appendTo( 'body' );

				/* eslint-disable camelcase */
				tinymce.init(
					{
						selector: `#${textareaId}`,
						init_instance_callback: function( instance ) {
							app._pluginManager = tinymce.PluginManager.get( 'wpeditimage' )( instance );
						},
					}
				);
				/* eslint-enable */
			}
		},

		/**
		 * Callback for TinyMCE init_instance_callback.
		 *
		 * @since 1.7.8
		 */
		bindInitInstanceCallback: function() {

			window.wpformsContentFieldTinyMCECallback = function( editor ) {

				if ( ! editor ) {
					return;
				}

				editor.on(
					'dirty',
					app.onContentUpdated
				);
				editor.on(
					'keyup',
					app.onContentUpdated
				);
			};

		},

		/**
		 * On content updated callback.
		 *
		 * @since 1.7.8
		 */
		onContentUpdated: function() {

			app.showUpdatePreviewButton( $( this ).attr( 'id' ) );
		},

		/**
		 * Set textarea visible when user clicks Text editor tab.
		 *
		 * @since 1.7.9
		 */
		setTextareaVisible: function() {

			let textareaID = $( this ).data( 'wp-editor-id' );

			$( `#${textareaID}` ).css( 'visibility', 'visible' );
		},

		/**
		 * Hide image toolbar when user click other field.
		 *
		 * @since 1.7.8
		 */
		hideImageToolbar: function() {
			$( '.mce-toolbar-grp.mce-inline-toolbar-grp' ).hide();
		},

		/**
		 * On insert media button clicked.
		 *
		 * @since 1.7.8
		 */
		onInsertMediaButtonClicked: function() {

			const textareaId = app.textareaId( $( this ).closest( '.wpforms-field-has-tinymce' ).data( 'field-id' ) );

			app.showUpdatePreviewButton( textareaId );
		},

		/**
		 * Flag saved state and show update preview button when editor content has been updated.
		 *
		 * @since 1.7.8
		 *
		 * @param {int} textareaId Textarea ID.
		 */
		showUpdatePreviewButton: function( textareaId ) {

			/**
			 * Unhide the "Update preview" button.
			 */
			$( `#${ textareaId }` ).closest( '.wpforms-field-option' ).find( '.update-preview' ).show();
		},

		/**
		 * Reset editor.
		 *
		 * Removes editor and adds it again.
		 *
		 * @since 1.7.8
		 *
		 * @param {int}    id    Field ID.
		 * @param {string} value Editor value to set after the reset.
		 */
		resetEditor: function( id, value ) {

			let textareaId = app.textareaId( id ),
				editor     = tinymce.get( textareaId );

			if ( editor ) {
				tinymce.execCommand( 'mceRemoveEditor', false, textareaId );
			} else {
				app.cleanEditorWrap( id );
			}

			app.initTinyMCE( id, value );
		},

		/**
		 * Initialize TinyMCE editor instance.
		 *
		 * @since 1.7.8
		 *
		 * @param {int}              id         Field ID.
		 * @param {undefined|string} value      Editor's value.
		 */
		initTinyMCE: function( id, value ) {

			let textareaId = app.textareaId( id );

			/*
			 Heads up, if you are going to edit editor settings, bear in mind editor is instantiated in two places:
			 - PHP instance in \WPForms\Admin\Builder\Traits\ContentInput::get_content_editor
			 - JS instance in WPForms.Admin.Builder.ContentField.initTinyMCE
			 */
			/* eslint-disable camelcase */
			tinymce.init(
				{
					selector: `#${textareaId}`,
					textarea_name: `fields[${id}][content]`,
					media_buttons: true,
					drag_drop_upload: true,
					relative_urls: false,
					remove_script_host: false,
					menubar: false,
					branding: false,
					object_resizing: false,
					height: wpforms_builder.content_field.editor_height,
					plugins: wpforms_builder.content_field.content_editor_plugins.join(),
					toolbar: wpforms_builder.content_field.content_editor_toolbar.join(),
					imagetools_toolbar: 'rotateleft rotateright | flipv fliph | editimage imageoptions',
					content_css: wpforms_builder.content_field.content_editor_css_url + '?' + new Date().getTime(), // https://www.tiny.cloud/docs-4x/configure/content-appearance/#browsercaching
					invalid_elements: wpforms_builder.content_field.invalid_elements,
					wp_shortcut_labels: window.wp?.editor?.getDefaultSettings?.()?.tinymce?.wp_shortcut_labels,
					body_class: wpforms_builder.content_field.body_class,
					init_instance_callback: function( instance ) {

						instance.setContent( typeof value !== 'undefined' ? value : wpforms_builder.content_field.editor_default_value );
						window.wpformsContentFieldTinyMCECallback( instance );
						app.setWrapperClasses( tinymce.$( `#${app.contentWrap( id )}` ) );
						quicktags(
							{
								id: textareaId,
								buttons: wpforms_builder.content_field.quicktags_buttons,
							}
						);
					},
				}
			);
			/* eslint-enable */
		},

		/**
		 * Checks if field has tinymce editor and editor object is instantiated.
		 *
		 * @since 1.7.8
		 *
		 * @param {string} type Field type.
		 *
		 * @returns {boolean} If has editor.
		 */
		hasEditor: function( type ) {

			return wpforms_builder.content_input.supported_field_types.includes( type ) && typeof tinymce !== 'undefined';
		},

		/**
		 * Clean editor wrap element from tinymce element which are about to be replaced with new one.
		 *
		 * @since 1.7.8
		 *
		 * @param {int} id Field ID.
		 */
		cleanEditorWrap: function( id ) {

			let textarea    = $( `#${app.textareaId( id )}` ),
				$wrap       = textarea.closest( '.wp-editor-wrap' ),
				editorTools = ( wp.template( 'wpforms-content-editor-tools' ) )( { optionId: `option-${id}` } );

			textarea.css( 'display', 'block' );
			app.setWrapperClasses( $wrap ).empty()
				.append( editorTools )
				.append( textarea )
				.attr( 'id', `${app.contentWrap( id )}` );
		},

		/**
		 * Set editor wrapper classes.
		 *
		 * @since 1.7.8
		 *
		 * @param {jQuery} $wrapper Editor wrapper element.
		 *
		 * @returns {jQuery} Editor wrapper element.
		 */
		setWrapperClasses: function( $wrapper ) {

			return $wrapper.addClass( 'tmce-active tmce-initialized' ).removeClass( 'html-active' );
		},

		/**
		 * Parse string content and replace [caption] shortcode.
		 *
		 * @since 1.7.8
		 *
		 * @param {string|undefined} content Text content to parse.
		 *
		 * @returns {string|undefined} Parsed text.
		 */
		parseShortcode: function( content ) {

			if ( typeof content === 'undefined' ) {
				return content;
			}

			return app._pluginManager?._do_shcode?.( content ) ?? content;
		},
	};

	return app;

}( document, window, jQuery ) );

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