/**
 * SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
 * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
 */

AUI.add(
	'liferay-portlet-dynamic-data-mapping-custom-fields',
	(A) => {
		const AArray = A.Array;

		const AEscape = A.Escape;

		const FormBuilderTextField = A.FormBuilderTextField;
		const FormBuilderTypes = A.FormBuilderField.types;

		const LiferayFormBuilderUtil = Liferay.FormBuilder.Util;

		const Lang = A.Lang;

		const booleanOptions = {
			false: 'No',
			true: 'Yes',
		};

		const booleanParse = A.DataType.Boolean.parse;
		const camelize = Lang.String.camelize;

		const editorLocalizedStrings = {
			cancel: 'Cancel',
			edit: 'Edit',
			save: 'Save',
		};

		const instanceOf = A.instanceOf;
		const isNull = Lang.isNull;
		const isObject = Lang.isObject;
		const isUndefined = Lang.isUndefined;
		const isValue = Lang.isValue;

		const structureFieldIndexEnable = function () {
			for (let i = 0; i < Liferay.Portlet.list.length; i++) {
				const indexableNode = A.one(
					'#_' + Liferay.Portlet.list[i] + '_indexable'
				);

				if (indexableNode) {
					const indexable = indexableNode.getAttribute('value');

					if (indexable === 'false') {
						return false;
					}
				}
			}

			return true;
		};

		const CSS_FIELD = A.getClassName('field');

		const CSS_FIELD_CHOICE = A.getClassName('field', 'choice');

		const CSS_FIELD_RADIO = A.getClassName('field', 'radio');

		const CSS_FORM_BUILDER_FIELD_NODE = A.getClassName(
			'form-builder-field',
			'node'
		);

		const CSS_RADIO = A.getClassName('radio');

		const DEFAULTS_FORM_VALIDATOR = A.config.FormValidator;

		const LOCALIZABLE_FIELD_ATTRS =
			Liferay.FormBuilder.LOCALIZABLE_FIELD_ATTRS;

		const RESTRICTED_NAME = 'submit';

		const STR_BLANK = '';

		const TPL_COLOR =
			'<input class="field form-control" type="text" value="' +
			A.Escape.html('Color') +
			'" readonly="readonly">';

		const TPL_GEOLOCATION =
			'<div class="field-labels-inline">' +
			'<img src="' +
			themeDisplay.getPathThemeImages() +
			'/common/geolocation.png" title="' +
			A.Escape.html('Geolocate') +
			'" />' +
			'<div>';

		const TPL_INPUT_BUTTON =
			'<div class="form-group">' +
			'<input class="field form-control" type="text" value="" readonly="readonly">' +
			'<div class="button-holder">' +
			'<button class="btn btn-secondary select-button" type="button">' +
			'<span class="lfr-btn-label">' +
			A.Escape.html('Select') +
			'</span>' +
			'</button>' +
			'</div>' +
			'</div>';

		const TPL_PARAGRAPH = '<p></p>';

		const TPL_RADIO =
			'<div class="' +
			CSS_RADIO +
			'">' +
			'<label class="radio-inline" for="{id}">' +
			'<input id="{id}" class="' +
			[
				CSS_FIELD,
				CSS_FIELD_CHOICE,
				CSS_FIELD_RADIO,
				CSS_FORM_BUILDER_FIELD_NODE,
			].join(' ') +
			'" name="{name}" type="radio" value="{value}" {checked} {disabled} />' +
			'{label}' +
			'</label>' +
			'</div>';

		const TPL_SEPARATOR = '<hr class="separator" />';

		const TPL_TEXT_HTML =
			'<textarea class="form-builder-field-node lfr-ddm-text-html"></textarea>';

		const TPL_WCM_IMAGE =
			'<div class="form-group">' +
			'<input class="field form-control" type="text" value="" readonly="readonly">' +
			'<div class="button-holder">' +
			'<button class="btn btn-secondary select-button" type="button">' +
			'<span class="lfr-btn-label">' +
			A.Escape.html('Select') +
			'</span>' +
			'</button>' +
			'</div>' +
			'<label class="control-label">' +
			A.Escape.html('Image\x20Description') +
			'</label>' +
			Liferay.Util.getLexiconIconTpl('asterisk') +
			'<input class="field form-control" type="text" value="" disabled>' +
			'</div>';

		const UNIQUE_FIELD_NAMES_MAP =
			Liferay.FormBuilder.UNIQUE_FIELD_NAMES_MAP;

		const UNLOCALIZABLE_FIELD_ATTRS =
			Liferay.FormBuilder.UNLOCALIZABLE_FIELD_ATTRS;

		DEFAULTS_FORM_VALIDATOR.STRINGS.structureDuplicateFieldName =
			'Please\x20enter\x20a\x20unique\x20field\x20name\x2e';

		DEFAULTS_FORM_VALIDATOR.RULES.structureDuplicateFieldName = function (
			value,
			editorNode
		) {
			const instance = this;

			const editingField = UNIQUE_FIELD_NAMES_MAP.getValue(value);

			const duplicate = editingField && !editingField.get('selected');

			if (duplicate) {
				editorNode.selectText(0, value.length);

				instance.resetField(editorNode);
			}

			return !duplicate;
		};

		DEFAULTS_FORM_VALIDATOR.STRINGS.structureFieldName =
			'Please\x20enter\x20only\x20alphanumeric\x20characters\x20or\x20underscore\x2e';

		DEFAULTS_FORM_VALIDATOR.RULES.structureFieldName = function (value) {
			return LiferayFormBuilderUtil.validateFieldName(value);
		};

		DEFAULTS_FORM_VALIDATOR.STRINGS.structureRestrictedFieldName = Lang.sub(
			'\x7b0\x7d\x20is\x20a\x20reserved\x20word\x20and\x20cannot\x20be\x20used\x2e',
			[RESTRICTED_NAME]
		);

		DEFAULTS_FORM_VALIDATOR.RULES.structureRestrictedFieldName = function (
			value
		) {
			return RESTRICTED_NAME !== value;
		};

		const applyStyles = function (node, styleContent) {
			const styles = styleContent.replace(/\n/g, STR_BLANK).split(';');

			node.setStyle(STR_BLANK);

			styles.forEach((item) => {
				const rule = item.split(':');

				if (rule.length === 2) {
					const key = camelize(rule[0]);
					const value = rule[1].trim();

					node.setStyle(key, value);
				}
			});
		};

		const ColorCellEditor = A.Component.create({
			EXTENDS: A.BaseCellEditor,

			NAME: 'color-cell-editor',

			prototype: {
				_defSaveFn() {
					const instance = this;

					const colorPicker = instance.get('colorPicker');

					const input = instance.get('boundingBox').one('input');

					if (/#[A-F\d]{6}/.test(input.val().toUpperCase())) {
						ColorCellEditor.superclass._defSaveFn.apply(
							instance,
							arguments
						);
					}
					else {
						colorPicker.show();
					}
				},

				_uiSetValue(val) {
					const instance = this;

					const input = instance.get('boundingBox').one('input');

					input.setStyle('color', val);
					input.val(val);

					instance.elements.val(val);
				},

				ELEMENT_TEMPLATE: '<input type="text" />',

				getElementsValue() {
					const instance = this;

					let retVal;

					const input = instance.get('boundingBox').one('input');

					if (input) {
						const val = input.val().toUpperCase();

						if (/#[A-F\d]{6}/.test(val) || val === '') {
							retVal = val;
						}
					}

					return retVal;
				},

				renderUI() {
					const instance = this;

					ColorCellEditor.superclass.renderUI.apply(
						instance,
						arguments
					);

					const input = instance.get('boundingBox').one('input');

					const colorPicker = new A.ColorPickerPopover({
						trigger: input,
						zIndex: 65535,
					}).render();

					colorPicker.on('select', (event) => {
						input.setStyle('color', event.color);
						input.val(event.color);

						instance.fire('save', {
							newVal: instance.getValue(),
							prevVal: event.color,
						});
					});

					instance.set('colorPicker', colorPicker);
				},
			},
		});

		const DLFileEntryCellEditor = A.Component.create({
			EXTENDS: A.BaseCellEditor,

			NAME: 'document-library-file-entry-cell-editor',

			prototype: {
				_defInitToolbarFn() {
					const instance = this;

					DLFileEntryCellEditor.superclass._defInitToolbarFn.apply(
						instance,
						arguments
					);

					instance.toolbar.add(
						{
							label: 'Select',
							on: {
								click: A.bind('_onClickChoose', instance),
							},
						},
						1
					);

					instance.toolbar.add(
						{
							label: 'Clear',
							on: {
								click: A.bind('_onClickClear', instance),
							},
						},
						2
					);
				},

				_getDocumentLibrarySelectorURL() {
					const instance = this;

					const portletNamespace = instance.get('portletNamespace');

					const criterionJSON = {
						desiredItemSelectorReturnTypes:
							'com.liferay.item.selector.criteria.FileEntryItemSelectorReturnType',
					};

					const uploadCriterionJSON = {
						URL: instance._getUploadURL(),
						desiredItemSelectorReturnTypes:
							'com.liferay.item.selector.criteria.FileEntryItemSelectorReturnType',
					};

					const documentLibrarySelectorParameters = {
						'0_json': JSON.stringify(criterionJSON),
						'1_json': JSON.stringify(criterionJSON),
						'2_json': JSON.stringify(uploadCriterionJSON),
						'criteria': 'file',
						'itemSelectedEventName':
							portletNamespace + 'selectDocumentLibrary',
						'p_p_id': Liferay.PortletKeys.ITEM_SELECTOR,
						'p_p_mode': 'view',
						'p_p_state': 'pop_up',
					};

					const documentLibrarySelectorURL =
						Liferay.Util.PortletURL.createPortletURL(
							themeDisplay.getLayoutRelativeControlPanelURL(),
							documentLibrarySelectorParameters
						);

					return documentLibrarySelectorURL.toString();
				},

				_getUploadURL() {
					const uploadParameters = {
						'cmd': 'add_temp',
						'javax.portlet.action':
							'/document_library/upload_file_entry',
						'p_auth': Liferay.authToken,
						'p_p_id': Liferay.PortletKeys.DOCUMENT_LIBRARY,
					};

					const uploadURL = Liferay.Util.PortletURL.createActionURL(
						themeDisplay.getLayoutRelativeControlPanelURL(),
						uploadParameters
					);

					return uploadURL.toString();
				},

				_isDocumentLibraryDialogOpen() {
					const instance = this;

					const portletNamespace = instance.get('portletNamespace');

					return !!Liferay.Util.getWindow(
						portletNamespace + 'selectDocumentLibrary'
					);
				},

				_onClickChoose() {
					const instance = this;

					const portletNamespace = instance.get('portletNamespace');

					Liferay.Util.openSelectionModal({
						onSelect: (selectedItem) => {
							if (selectedItem) {
								const itemValue = JSON.parse(
									selectedItem.value
								);

								instance._selectFileEntry(
									itemValue.groupId,
									itemValue.title,
									itemValue.uuid
								);
							}
						},
						selectEventName:
							portletNamespace + 'selectDocumentLibrary',
						title: 'Select\x20File',
						url: instance._getDocumentLibrarySelectorURL(),
					});
				},

				_onClickClear() {
					const instance = this;

					instance.set('value', STR_BLANK);
				},

				_onDocMouseDownExt(event) {
					const instance = this;

					const boundingBox = instance.get('boundingBox');

					const documentLibraryDialogOpen =
						instance._isDocumentLibraryDialogOpen();

					if (
						!documentLibraryDialogOpen &&
						!boundingBox.contains(event.target)
					) {
						instance.set('visible', false);
					}
				},

				_selectFileEntry(groupId, title, uuid) {
					const instance = this;

					instance.set(
						'value',
						JSON.stringify({
							groupId,
							title,
							uuid,
						})
					);
				},

				_syncElementsFocus() {
					const instance = this;

					const boundingBox = instance.toolbar.get('boundingBox');

					const button = boundingBox.one('button');

					if (button) {
						button.focus();
					}
					else {
						DLFileEntryCellEditor.superclass._syncElementsFocus.apply(
							instance,
							arguments
						);
					}
				},

				_syncFileLabel(title, url) {
					const instance = this;

					const contentBox = instance.get('contentBox');

					let linkNode = contentBox.one('a');

					if (!linkNode) {
						linkNode = A.Node.create('<a></a>');

						contentBox.prepend(linkNode);
					}

					linkNode.setAttribute('href', url);
					linkNode.setContent(Liferay.Util.escapeHTML(title));
				},

				_uiSetValue(val) {
					const instance = this;

					if (val) {
						LiferayFormBuilderUtil.getFileEntry(
							val,
							(fileEntry) => {
								const url =
									LiferayFormBuilderUtil.getFileEntryURL(
										fileEntry
									);

								instance._syncFileLabel(fileEntry.title, url);
							}
						);
					}
					else {
						instance._syncFileLabel(STR_BLANK, STR_BLANK);

						val = STR_BLANK;
					}

					instance.elements.val(val);
				},

				ELEMENT_TEMPLATE: '<input type="hidden" />',

				getElementsValue() {
					const instance = this;

					return instance.get('value');
				},
			},
		});

		const IntegerCellEditor = A.Component.create({
			EXTENDS: A.TextCellEditor,

			NAME: 'text-cell-editor',

			prototype: {
				ELEMENT_TEMPLATE: '<input type="text" />',

				getElementsValue() {
					const instance = this;

					let retVal;

					const input = instance.get('boundingBox').one('input');

					if (input) {
						const val = input.val();

						if (/^[+-]?(\d+)*$/.test(val) || val === '') {
							retVal = val;
						}
					}

					if (retVal) {
						return retVal;
					}
					else {
						instance.fire('save', {
							newVal: '',
							prevVal: retVal,
						});
					}
				},
			},
		});

		const JournalArticleCellEditor = A.Component.create({
			EXTENDS: A.BaseCellEditor,

			NAME: 'journal-article-cell-editor',

			prototype: {
				_defInitToolbarFn() {
					const instance = this;

					JournalArticleCellEditor.superclass._defInitToolbarFn.apply(
						instance,
						arguments
					);

					instance.toolbar.add(
						{
							label: 'Select',
							on: {
								click: A.bind('_onClickChoose', instance),
							},
						},
						1
					);

					instance.toolbar.add(
						{
							label: 'Clear',
							on: {
								click: A.bind('_onClickClear', instance),
							},
						},
						2
					);
				},

				_getWebContentSelectorURL() {
					const instance = this;

					const portletNamespace = instance.get('portletNamespace');

					const criterionJSON = {
						desiredItemSelectorReturnTypes:
							'com.liferay.item.selector.criteria.JournalArticleItemSelectorReturnType',
					};

					const webContentSelectorParameters = {
						'0_json': JSON.stringify(criterionJSON),
						'criteria':
							'com.liferay.item.selector.criteria.info.item.criterion.InfoItemItemSelectorCriterion',
						'itemSelectedEventName':
							portletNamespace + 'selectDocumentLibrary',
						'p_auth': Liferay.authToken,
						'p_p_id': Liferay.PortletKeys.ITEM_SELECTOR,
						'p_p_mode': 'view',
						'p_p_state': 'pop_up',
					};

					const webContentSelectorURL =
						Liferay.Util.PortletURL.createRenderURL(
							themeDisplay.getLayoutRelativeControlPanelURL(),
							webContentSelectorParameters
						);

					return webContentSelectorURL.toString();
				},

				_handleCancelEvent() {
					const instance = this;

					instance.get('boundingBox').hide();
				},

				_handleSaveEvent() {
					const instance = this;

					JournalArticleCellEditor.superclass._handleSaveEvent.apply(
						instance,
						arguments
					);

					instance.get('boundingBox').hide();
				},

				_onClickChoose() {
					const instance = this;

					const portletNamespace = instance.get('portletNamespace');

					Liferay.Util.openSelectionModal({
						onSelect: (selectedItem) => {
							if (selectedItem) {
								const itemValue = JSON.parse(
									selectedItem.value
								);

								instance.setValue({
									className: itemValue.className,
									classPK: itemValue.classPK,
									title: itemValue.title,
								});
							}
						},
						selectEventName:
							portletNamespace + 'selectDocumentLibrary',
						title: 'Web\x20Content',
						url: instance._getWebContentSelectorURL(),
					});
				},

				_onClickClear() {
					const instance = this;

					instance.set('value', STR_BLANK);
				},

				_onDocMouseDownExt(event) {
					const instance = this;

					const boundingBox = instance.get('boundingBox');

					if (!boundingBox.contains(event.target)) {
						instance._handleCancelEvent(event);
					}
				},

				_syncJournalArticleLabel(title) {
					const instance = this;

					const contentBox = instance.get('contentBox');

					let linkNode = contentBox.one('span');

					if (!linkNode) {
						linkNode = A.Node.create('<span></span>');

						contentBox.prepend(linkNode);
					}

					linkNode.setContent(Liferay.Util.escapeHTML(title));
				},

				_uiSetValue(val) {
					const instance = this;

					if (val) {
						val = JSON.parse(val);
						const title =
							'Web\x20Content' +
							': ' +
							val.classPK;

						instance._syncJournalArticleLabel(title);
					}
					else {
						instance._syncJournalArticleLabel(STR_BLANK);
					}
				},

				ELEMENT_TEMPLATE: '<input type="hidden" />',

				getElementsValue() {
					const instance = this;

					return instance.get('value');
				},

				getParsedValue(value) {
					if (Lang.isString(value)) {
						if (value !== '') {
							value = JSON.parse(value);
						}
						else {
							value = {};
						}
					}

					return value;
				},

				setValue(value) {
					const instance = this;

					const parsedValue = instance.getParsedValue(value);

					if (!parsedValue.className && !parsedValue.classPK) {
						value = '';
					}
					else {
						value = JSON.stringify(parsedValue);
					}

					instance.set('value', value);
				},
			},
		});

		const NumberCellEditor = A.Component.create({
			EXTENDS: A.TextCellEditor,

			NAME: 'text-cell-editor',

			prototype: {
				ELEMENT_TEMPLATE: '<input type="text" />',

				getElementsValue() {
					const instance = this;

					let retVal;

					const input = instance.get('boundingBox').one('input');

					if (input) {
						const val = input.val();

						if (/^[+-]?(\d+)([.,]\d+)*$/.test(val) || val === '') {
							retVal = val;
						}
					}

					if (retVal) {
						return retVal;
					}
					else {
						instance.fire('save', {
							newVal: '',
							prevVal: retVal,
						});
					}
				},
			},
		});

		Liferay.FormBuilder.CUSTOM_CELL_EDITORS = {};

		const customCellEditors = [
			ColorCellEditor,
			DLFileEntryCellEditor,
			IntegerCellEditor,
			JournalArticleCellEditor,
			NumberCellEditor,
		];

		customCellEditors.forEach((item) => {
			Liferay.FormBuilder.CUSTOM_CELL_EDITORS[item.NAME] = item;
		});

		const LiferayFieldSupport = function () {};

		LiferayFieldSupport.ATTRS = {
			autoGeneratedName: {
				setter: booleanParse,
				value: true,
			},

			indexType: {
				valueFn() {
					return structureFieldIndexEnable() ? 'keyword' : '';
				},
			},

			localizable: {
				setter: booleanParse,
				value: true,
			},

			name: {
				setter: LiferayFormBuilderUtil.normalizeKey,
				validator(val) {
					return !UNIQUE_FIELD_NAMES_MAP.has(val);
				},
				valueFn() {
					const instance = this;

					let label = LiferayFormBuilderUtil.normalizeKey(
						instance.get('label')
					);

					label = label.replace(/[^a-z0-9]/gi, '');

					let name = label + instance._randomString(4);

					while (UNIQUE_FIELD_NAMES_MAP.has(name)) {
						name = A.FormBuilderField.buildFieldName(name);
					}

					return name;
				},
			},

			repeatable: {
				setter: booleanParse,
				value: false,
			},
		};

		LiferayFieldSupport.prototype.initializer = function () {
			const instance = this;

			instance.after('nameChange', instance._afterNameChange);
		};

		LiferayFieldSupport.prototype._afterNameChange = function (event) {
			const instance = this;

			UNIQUE_FIELD_NAMES_MAP.remove(event.prevVal);
			UNIQUE_FIELD_NAMES_MAP.put(event.newVal, instance);
		};

		LiferayFieldSupport.prototype._handleDeleteEvent = function (event) {
			const instance = this;

			const strings = instance.getStrings();

			const deleteModal = Liferay.Util.Window.getWindow({
				dialog: {
					bodyContent: strings.deleteFieldsMessage,
					destroyOnHide: true,
					height: 200,
					resizable: false,
					toolbars: {
						footer: [
							{
								cssClass: 'btn-primary',
								label: 'OK',
								on: {
									click() {
										instance.destroy();

										deleteModal.hide();
									},
								},
							},
							{
								label: 'Cancel',
								on: {
									click() {
										deleteModal.hide();
									},
								},
							},
						],
					},
					width: 700,
				},
				title: instance.get('label'),
			})
				.render()
				.show();

			event.stopPropagation();
		};

		LiferayFieldSupport.prototype._randomString = function (length) {
			const randomString = Math.ceil(
				Math.random() * Number.MAX_SAFE_INTEGER
			).toString(36);

			return randomString.substring(0, length);
		};

		const LocalizableFieldSupport = function () {};

		LocalizableFieldSupport.ATTRS = {
			localizationMap: {
				setter: A.clone,
				value: {},
			},

			readOnlyAttributes: {
				getter: '_getReadOnlyAttributes',
			},
		};

		LocalizableFieldSupport.prototype.initializer = function () {
			const instance = this;

			const builder = instance.get('builder');

			instance.after('render', instance._afterLocalizableFieldRender);

			LOCALIZABLE_FIELD_ATTRS.forEach((localizableField) => {
				instance.after(
					localizableField + 'Change',
					instance._afterLocalizableFieldChange
				);
			});

			builder.translationManager.after(
				'editingLocaleChange',
				instance._afterEditingLocaleChange,
				instance
			);
		};

		LocalizableFieldSupport.prototype._afterEditingLocaleChange = function (
			event
		) {
			const instance = this;

			instance._syncLocaleUI(event.newVal);
		};

		LocalizableFieldSupport.prototype._afterLocalizableFieldChange =
			function (event) {
				const instance = this;

				const builder = instance.get('builder');

				const translationManager = builder.translationManager;

				const editingLocale = translationManager.get('editingLocale');

				instance._updateLocalizationMapAttribute(
					editingLocale,
					event.attrName
				);
			};

		LocalizableFieldSupport.prototype._afterLocalizableFieldRender =
			function () {
				const instance = this;

				const builder = instance.get('builder');

				const translationManager = builder.translationManager;

				const editingLocale = translationManager.get('editingLocale');

				instance._updateLocalizationMap(editingLocale);
			};

		LocalizableFieldSupport.prototype._getReadOnlyAttributes = function (
			val
		) {
			const instance = this;

			const builder = instance.get('builder');

			const translationManager = builder.translationManager;

			const defaultLocale = translationManager.get('defaultLocale');
			const editingLocale = translationManager.get('editingLocale');

			if (defaultLocale !== editingLocale) {
				val = UNLOCALIZABLE_FIELD_ATTRS.concat(val);
			}

			return AArray.dedupe(val);
		};

		LocalizableFieldSupport.prototype._syncLocaleUI = function (locale) {
			const instance = this;

			const builder = instance.get('builder');

			const localizationMap = instance.get('localizationMap');

			const translationManager = builder.translationManager;

			let defaultLocale = themeDisplay.getDefaultLanguageId();

			if (translationManager) {
				defaultLocale = translationManager.get('defaultLocale');
			}

			const localeMap =
				localizationMap[locale] || localizationMap[defaultLocale];

			if (isObject(localeMap)) {
				LOCALIZABLE_FIELD_ATTRS.forEach((item) => {
					if (item !== 'options') {
						const localizedItem = localeMap[item];

						if (
							!isUndefined(localizedItem) &&
							!isNull(localizedItem)
						) {
							instance.set(item, localizedItem);
						}
					}
				});

				builder._syncUniqueField(instance);
			}

			if (instanceOf(instance, A.FormBuilderMultipleChoiceField)) {
				instance._syncOptionsLocaleUI(locale);
			}

			if (builder.editingField === instance) {
				builder.propertyList.set('data', instance.getProperties());
			}
		};

		LocalizableFieldSupport.prototype._syncOptionsLocaleUI = function (
			locale
		) {
			const instance = this;

			const options = instance.get('options');

			options.forEach((item) => {
				const localizationMap = item.localizationMap;

				if (isObject(localizationMap)) {
					const localeMap = localizationMap[locale];

					if (isObject(localeMap)) {
						item.label = localeMap.label;
					}
				}
			});

			instance.set('options', options);
		};

		LocalizableFieldSupport.prototype._updateLocalizationMap = function (
			locale
		) {
			const instance = this;

			LOCALIZABLE_FIELD_ATTRS.forEach((item) => {
				instance._updateLocalizationMapAttribute(locale, item);
			});
		};

		LocalizableFieldSupport.prototype._updateLocalizationMapAttribute =
			function (locale, attributeName) {
				const instance = this;

				if (attributeName === 'options') {
					instance._updateLocalizationMapOptions(locale);
				}
				else {
					const localizationMap = instance.get('localizationMap');

					const localeMap = localizationMap[locale] || {};

					localeMap[attributeName] = instance.get(attributeName);

					localizationMap[locale] = localeMap;

					instance.set('localizationMap', localizationMap);
				}
			};

		LocalizableFieldSupport.prototype._updateLocalizationMapOptions =
			function (locale) {
				const instance = this;

				const options = instance.get('options');

				if (options) {
					options.forEach((item) => {
						let localizationMap = item.localizationMap;

						if (!isObject(localizationMap)) {
							localizationMap = {};
						}

						localizationMap[locale] = {
							label: item.label,
						};

						item.localizationMap = localizationMap;
					});
				}
			};

		const SerializableFieldSupport = function () {};

		SerializableFieldSupport.prototype._addDefinitionFieldLocalizedAttributes =
			function (fieldJSON) {
				const instance = this;

				LOCALIZABLE_FIELD_ATTRS.forEach((attr) => {
					if (attr === 'options') {
						if (
							instanceOf(
								instance,
								A.FormBuilderMultipleChoiceField
							)
						) {
							instance._addDefinitionFieldOptions(fieldJSON);
						}
					}
					else {
						fieldJSON[attr] = instance._getLocalizedValue(attr);
					}
				});
			};

		SerializableFieldSupport.prototype._addDefinitionFieldUnlocalizedAttributes =
			function (fieldJSON) {
				const instance = this;

				UNLOCALIZABLE_FIELD_ATTRS.forEach((attr) => {
					fieldJSON[attr] = instance.get(attr);
				});
			};

		SerializableFieldSupport.prototype._addDefinitionFieldOptions =
			function (fieldJSON) {
				const instance = this;

				const options = instance.get('options');

				const fieldOptions = [];

				if (options) {
					const builder = instance.get('builder');

					const translationManager = builder.translationManager;

					const availableLocales =
						translationManager.get('availableLocales');

					options.forEach((option) => {
						const fieldOption = {};

						const localizationMap = option.localizationMap;

						fieldOption.value = option.value;
						fieldOption.label = {};

						availableLocales.forEach((locale) => {
							const label = instance._getValue(
								'label',
								locale,
								localizationMap
							);

							fieldOption.label[locale] =
								LiferayFormBuilderUtil.normalizeValue(label);
						});

						fieldOptions.push(fieldOption);
					});

					fieldJSON.options = fieldOptions;
				}
			};

		SerializableFieldSupport.prototype._addDefinitionFieldNestedFields =
			function (fieldJSON) {
				const instance = this;

				const nestedFields = [];

				instance.get('fields').each((childField) => {
					nestedFields.push(childField.serialize());
				});

				if (nestedFields.length) {
					fieldJSON.nestedFields = nestedFields;
				}
			};

		SerializableFieldSupport.prototype._getLocalizedValue = function (
			attribute
		) {
			const instance = this;

			const builder = instance.get('builder');

			const localizationMap = instance.get('localizationMap');

			const localizedValue = {};

			const translationManager = builder.translationManager;

			translationManager.get('availableLocales').forEach((locale) => {
				localizedValue[locale] = LiferayFormBuilderUtil.normalizeValue(
					instance._getValue(attribute, locale, localizationMap)
				);
			});

			return localizedValue;
		};

		SerializableFieldSupport.prototype._getValue = function (
			attribute,
			locale,
			localizationMap
		) {
			const instance = this;

			const builder = instance.get('builder');

			const translationManager = builder.translationManager;

			const defaultLocale = translationManager.get('defaultLocale');

			// eslint-disable-next-line @liferay/aui/no-object
			let value = A.Object.getValue(localizationMap, [locale, attribute]);

			if (isValue(value)) {
				return value;
			}

			// eslint-disable-next-line @liferay/aui/no-object
			value = A.Object.getValue(localizationMap, [
				defaultLocale,
				attribute,
			]);

			if (isValue(value)) {
				return value;
			}

			for (const localizationMapLocale in localizationMap) {

				// eslint-disable-next-line @liferay/aui/no-object
				value = A.Object.getValue(localizationMap, [
					localizationMapLocale,
					attribute,
				]);

				if (isValue(value)) {
					return value;
				}
			}

			return STR_BLANK;
		};

		SerializableFieldSupport.prototype.serialize = function () {
			const instance = this;

			const fieldJSON = {};

			instance._addDefinitionFieldLocalizedAttributes(fieldJSON);
			instance._addDefinitionFieldUnlocalizedAttributes(fieldJSON);
			instance._addDefinitionFieldNestedFields(fieldJSON);

			return fieldJSON;
		};

		A.Base.mix(A.FormBuilderField, [
			LiferayFieldSupport,
			LocalizableFieldSupport,
			SerializableFieldSupport,
		]);

		const FormBuilderProto = A.FormBuilderField.prototype;

		const originalGetPropertyModel = FormBuilderProto.getPropertyModel;

		FormBuilderProto.getPropertyModel = function () {
			const instance = this;

			const model = originalGetPropertyModel.call(instance);

			const type = instance.get('type');

			let indexTypeOptions = {
				'': 'No',
				'keyword': 'Yes',
			};

			if (type === 'ddm-image' || type === 'text') {
				indexTypeOptions = {
					'': 'Not\x20Indexable',
					'keyword': 'Indexable\x20-\x20Keyword',
					'text': 'Indexable\x20-\x20Text',
				};
			}

			if (type === 'ddm-text-html' || type === 'textarea') {
				indexTypeOptions = {
					'': 'Not\x20Indexable',
					'text': 'Indexable\x20-\x20Text',
				};
			}

			const newModel = [];

			model.forEach((item) => {
				if (item.attributeName === 'name') {
					item.editor = new A.TextCellEditor({
						validator: {
							rules: {
								value: {
									required: true,
									structureDuplicateFieldName: true,
									structureFieldName: true,
									structureRestrictedFieldName: true,
								},
							},
						},
					});
				}

				if (item.editor) {
					item.editor.set('strings', editorLocalizedStrings);
				}

				newModel.push(item);

				if (item.attributeName === 'required') {
					item.id = 'required';

					if (type === 'ddm-image') {
						newModel.push(
							instance.getRequiredDescriptionPropertyModel()
						);
					}
				}
			});

			return newModel.concat([
				{
					attributeName: 'indexType',
					editor: new A.RadioCellEditor({
						options: indexTypeOptions,
						strings: editorLocalizedStrings,
					}),
					formatter(val) {
						return indexTypeOptions[val.data.value];
					},
					name: 'Indexable',
				},
				{
					attributeName: 'localizable',
					editor: new A.RadioCellEditor({
						options: booleanOptions,
						strings: editorLocalizedStrings,
					}),
					formatter(val) {
						return booleanOptions[val.data.value];
					},
					name: 'Localizable',
				},
				{
					attributeName: 'repeatable',
					editor: new A.RadioCellEditor({
						options: booleanOptions,
						strings: editorLocalizedStrings,
					}),
					formatter(val) {
						return booleanOptions[val.data.value];
					},
					name: 'Repeatable',
				},
			]);
		};

		const DDMColorField = A.Component.create({
			ATTRS: {
				dataType: {
					value: 'color',
				},

				fieldNamespace: {
					value: 'ddm',
				},

				showLabel: {
					value: false,
				},
			},

			EXTENDS: A.FormBuilderField,

			NAME: 'ddm-color',

			prototype: {
				getHTML() {
					return TPL_COLOR;
				},

				getPropertyModel() {
					const instance = this;

					const model =
						DDMColorField.superclass.getPropertyModel.apply(
							instance,
							arguments
						);

					model.forEach((item, index, collection) => {
						const attributeName = item.attributeName;

						if (attributeName === 'predefinedValue') {
							collection[index] = {
								attributeName,
								editor: new ColorCellEditor({
									strings: editorLocalizedStrings,
								}),
								name: 'Predefined\x20Value',
							};
						}
					});

					return model;
				},
			},
		});

		const DDMDateField = A.Component.create({
			ATTRS: {
				dataType: {
					value: 'date',
				},

				fieldNamespace: {
					value: 'ddm',
				},
			},

			EXTENDS: A.FormBuilderTextField,

			NAME: 'ddm-date',

			prototype: {
				getPropertyModel() {
					const instance = this;

					const model =
						DDMDateField.superclass.getPropertyModel.apply(
							instance,
							arguments
						);

					model.forEach((item, index, collection) => {
						const attributeName = item.attributeName;

						if (attributeName === 'predefinedValue') {
							collection[index] = {
								attributeName,
								editor: new A.DateCellEditor({
									dateFormat: '%m/%d/%Y',
									inputFormatter(val) {
										const instance = this;

										let value = val;

										if (Array.isArray(val)) {
											value = instance.formatDate(val[0]);
										}

										return value;
									},

									outputFormatter(val) {
										const instance = this;

										let retVal = val;

										if (Array.isArray(val)) {
											const formattedValue =
												A.DataType.Date.parse(
													instance.get('dateFormat'),
													val[0]
												);

											retVal = [formattedValue];
										}

										return retVal;
									},
								}),
								name: 'Predefined\x20Value',
								strings: editorLocalizedStrings,
							};
						}
					});

					return model;
				},

				renderUI() {
					const instance = this;

					DDMDateField.superclass.renderUI.apply(instance, arguments);

					let keysPressed = {};

					const onKeyDown = function (domEvent) {
						if (domEvent.keyCode === 16) {
							keysPressed[domEvent.keyCode] = true;
						}
					};

					const onKeyUp = function (domEvent) {
						if (domEvent.keyCode === 16) {
							delete keysPressed[domEvent.keyCode];
						}
					};

					const trigger = instance.get('templateNode').one('input');

					const closePopoverOnKeyboardNavigation = function (
						instance
					) {
						instance.hide();

						keysPressed = {};

						if (trigger) {
							Liferay.Util.focusFormField(trigger);
						}
					};

					if (trigger) {
						instance.datePicker = new A.DatePickerDeprecated({
							calendar: {
								locale: Liferay.ThemeDisplay.getLanguageId(),
							},
							on: {
								destroy() {
									document.removeEventListener(
										'keydown',
										onKeyDown
									);
									document.removeEventListener(
										'keyup',
										onKeyUp
									);
								},
								enterKey() {
									let countInterval = 0;

									const intervalId = setInterval(() => {
										const trigger = A.one(
											'.datepicker-popover:not(.popover-hidden) .yui3-calendarnav-prevmonth'
										);

										if (trigger) {
											Liferay.Util.focusFormField(
												trigger
											);
											clearInterval(intervalId);
										}
										else if (countInterval > 10) {
											clearInterval(intervalId);
										}
										countInterval++;
									}, 100);
								},
								init() {
									document.addEventListener(
										'keydown',
										onKeyDown
									);
									document.addEventListener('keyup', onKeyUp);
								},
								selectionChange(event) {
									const date = event.newSelection;

									instance.setValue(A.Date.format(date));
								},
							},
							popover: {
								on: {
									keydown(event) {
										const instance = this;

										const domEvent = event.domEvent;

										keysPressed[domEvent.keyCode] = true;

										const isTabPressed =
											domEvent.keyCode === 9 ||
											keysPressed[9];

										const isShiftPressed =
											domEvent.keyCode === 16 ||
											keysPressed[16];

										const isForwardNavigation =
											isTabPressed && !isShiftPressed;

										const isEscapePressed =
											domEvent.keyCode === 27 ||
											keysPressed[27];

										const hasClassName =
											domEvent.target.hasClass(
												'yui3-calendar-grid'
											) ||
											domEvent.target.hasClass(
												'yui3-calendar-day'
											);

										if (
											(isForwardNavigation &&
												hasClassName) ||
											isEscapePressed
										) {
											closePopoverOnKeyboardNavigation(
												instance
											);
										}
									},
									keyup(event) {
										const instance = this;

										const domEvent = event.domEvent;

										const isTabPressed =
											domEvent.keyCode === 9 ||
											keysPressed[9];

										const isShiftPressed =
											domEvent.keyCode === 16 ||
											keysPressed[16];

										const isBackwardNavigation =
											isTabPressed && isShiftPressed;

										const hasClassName =
											domEvent.target.hasClass(
												'yui3-calendar-focused'
											);

										if (
											isBackwardNavigation &&
											hasClassName
										) {
											closePopoverOnKeyboardNavigation(
												instance
											);
										}

										delete keysPressed[domEvent.keyCode];
									},
								},
							},
							trigger,
						}).render();
					}

					instance.datePicker.calendar.set('strings', {
						next: 'Next',
						none: 'None',
						previous: 'Previous',
						today: 'Today',
					});
				},
			},
		});

		const DDMDecimalField = A.Component.create({
			ATTRS: {
				dataType: {
					value: 'decimal',
				},

				fieldNamespace: {
					value: 'ddm',
				},
			},

			EXTENDS: A.FormBuilderTextField,

			NAME: 'ddm-decimal',

			prototype: {
				getPropertyModel() {
					const instance = this;

					const model =
						DDMDecimalField.superclass.getPropertyModel.apply(
							instance,
							arguments
						);

					model.forEach((item, index, collection) => {
						const attributeName = item.attributeName;

						if (attributeName === 'predefinedValue') {
							collection[index] = {
								attributeName,
								editor: new NumberCellEditor({
									strings: editorLocalizedStrings,
								}),
								name: 'Predefined\x20Value',
							};
						}
					});

					return model;
				},
			},
		});

		const DDMDocumentLibraryField = A.Component.create({
			ATTRS: {
				dataType: {
					value: 'document-library',
				},

				fieldNamespace: {
					value: 'ddm',
				},
			},

			EXTENDS: A.FormBuilderField,

			NAME: 'ddm-documentlibrary',

			prototype: {
				_defaultFormatter() {
					return 'documents-and-media';
				},

				_uiSetValue() {
					return 'Select';
				},

				getHTML() {
					return TPL_INPUT_BUTTON;
				},

				getPropertyModel() {
					const instance = this;

					const model =
						DDMDocumentLibraryField.superclass.getPropertyModel.apply(
							instance,
							arguments
						);

					model.forEach((item) => {
						const attributeName = item.attributeName;

						if (attributeName === 'predefinedValue') {
							item.editor = new DLFileEntryCellEditor({
								strings: editorLocalizedStrings,
							});

							item.formatter = function (object) {
								const data = object.data;

								let label = STR_BLANK;

								const value = data.value;

								if (value !== STR_BLANK) {
									label =
										'(' +
										'File' +
										')';
								}

								return label;
							};
						}
						else if (attributeName === 'type') {
							item.formatter = instance._defaultFormatter;
						}
					});

					return model;
				},
			},
		});

		const DDMGeolocationField = A.Component.create({
			ATTRS: {
				dataType: {
					value: 'geolocation',
				},

				fieldNamespace: {
					value: 'ddm',
				},

				localizable: {
					setter: booleanParse,
					value: false,
				},
			},

			EXTENDS: A.FormBuilderField,

			NAME: 'ddm-geolocation',

			prototype: {
				getHTML() {
					return TPL_GEOLOCATION;
				},

				getPropertyModel() {
					const instance = this;

					return DDMGeolocationField.superclass.getPropertyModel
						.apply(instance, arguments)
						.filter((item) => {
							return item.attributeName !== 'predefinedValue';
						});
				},
			},
		});

		const DDMImageField = A.Component.create({
			ATTRS: {
				dataType: {
					value: 'image',
				},

				fieldNamespace: {
					value: 'ddm',
				},

				indexType: {
					valueFn() {
						return structureFieldIndexEnable() ? 'text' : '';
					},
				},

				requiredDescription: {
					setter: booleanParse,
					value: true,
				},
			},

			EXTENDS: A.FormBuilderField,

			NAME: 'ddm-image',

			prototype: {
				getHTML() {
					return TPL_WCM_IMAGE;
				},

				getRequiredDescriptionPropertyModel() {
					return {
						attributeName: 'requiredDescription',
						editor: new A.RadioCellEditor({
							options: booleanOptions,
							strings: editorLocalizedStrings,
						}),
						formatter(val) {
							return booleanOptions[val.data.value];
						},
						id: 'requiredDescription',
						name: 'Required\x20Description',
					};
				},
			},
		});

		const DDMIntegerField = A.Component.create({
			ATTRS: {
				dataType: {
					value: 'integer',
				},

				fieldNamespace: {
					value: 'ddm',
				},
			},

			EXTENDS: A.FormBuilderTextField,

			NAME: 'ddm-integer',

			prototype: {
				getPropertyModel() {
					const instance = this;

					const model =
						DDMIntegerField.superclass.getPropertyModel.apply(
							instance,
							arguments
						);

					model.forEach((item, index, collection) => {
						const attributeName = item.attributeName;

						if (attributeName === 'predefinedValue') {
							collection[index] = {
								attributeName,
								editor: new IntegerCellEditor({
									strings: editorLocalizedStrings,
								}),
								name: 'Predefined\x20Value',
							};
						}
					});

					return model;
				},
			},
		});

		const DDMNumberField = A.Component.create({
			ATTRS: {
				dataType: {
					value: 'number',
				},

				fieldNamespace: {
					value: 'ddm',
				},
			},

			EXTENDS: A.FormBuilderTextField,

			NAME: 'ddm-number',

			prototype: {
				getPropertyModel() {
					const instance = this;

					const model =
						DDMIntegerField.superclass.getPropertyModel.apply(
							instance,
							arguments
						);

					model.forEach((item, index, collection) => {
						const attributeName = item.attributeName;

						if (attributeName === 'predefinedValue') {
							collection[index] = {
								attributeName,
								editor: new NumberCellEditor({
									strings: editorLocalizedStrings,
								}),
								name: 'Predefined\x20Value',
							};
						}
					});

					return model;
				},
			},
		});

		const DDMParagraphField = A.Component.create({
			ATTRS: {
				dataType: {
					value: undefined,
				},

				fieldNamespace: {
					value: 'ddm',
				},

				showLabel: {
					readOnly: true,
					value: true,
				},

				style: {
					value: STR_BLANK,
				},
			},

			EXTENDS: A.FormBuilderField,

			NAME: 'ddm-paragraph',

			UI_ATTRS: ['label', 'style'],

			prototype: {
				_uiSetLabel(val) {
					const instance = this;

					instance.get('templateNode').setContent(val);
				},

				_uiSetStyle(val) {
					const instance = this;

					const templateNode = instance.get('templateNode');

					applyStyles(templateNode, val);
				},

				getHTML() {
					return TPL_PARAGRAPH;
				},

				getPropertyModel() {
					return [
						{
							attributeName: 'type',
							editor: false,
							name: 'Type',
						},
						{
							attributeName: 'label',
							editor: new A.TextAreaCellEditor({
								strings: editorLocalizedStrings,
							}),
							name: 'Text',
						},
						{
							attributeName: 'style',
							editor: new A.TextAreaCellEditor({
								strings: editorLocalizedStrings,
							}),
							name: 'Style',
						},
					];
				},
			},
		});

		const DDMRadioField = A.Component.create({
			ATTRS: {
				dataType: {
					value: 'radio',
				},

				predefinedValue: {
					setter(val) {
						return val;
					},
				},
			},

			EXTENDS: A.FormBuilderRadioField,

			NAME: 'ddm-radio',

			OVERRIDE_TYPE: 'radio',

			prototype: {
				_uiSetOptions(val) {
					const instance = this;

					const buffer = [];
					let counter = 0;

					const predefinedValue = instance.get('predefinedValue');
					const templateNode = instance.get('templateNode');

					A.each(val, (item) => {
						const checked = predefinedValue === item.value;

						buffer.push(
							Lang.sub(TPL_RADIO, {
								checked: checked ? 'checked="checked"' : '',
								disabled: instance.get('disabled')
									? 'disabled="disabled"'
									: '',
								id: AEscape.html(
									instance.get('id') + counter++
								),
								label: AEscape.html(item.label),
								name: AEscape.html(instance.get('name')),
								value: AEscape.html(item.value),
							})
						);
					});

					instance.optionNodes = A.NodeList.create(buffer.join(''));

					templateNode.setContent(instance.optionNodes);
				},

				_uiSetPredefinedValue(val) {
					const instance = this;

					const optionNodes = instance.optionNodes;

					if (!optionNodes) {
						return;
					}

					optionNodes.set('checked', false);

					optionNodes
						.all('input[value="' + AEscape.html(val) + '"]')
						.set('checked', true);
				},
			},
		});

		const DDMSeparatorField = A.Component.create({
			ATTRS: {
				dataType: {
					value: undefined,
				},

				fieldNamespace: {
					value: 'ddm',
				},

				showLabel: {
					value: false,
				},

				style: {
					value: STR_BLANK,
				},
			},

			EXTENDS: A.FormBuilderField,

			NAME: 'ddm-separator',

			UI_ATTRS: ['style'],

			prototype: {
				_uiSetStyle(val) {
					const instance = this;

					const templateNode = instance.get('templateNode');

					applyStyles(templateNode, val);
				},

				getHTML() {
					return TPL_SEPARATOR;
				},

				getPropertyModel() {
					const instance = this;

					const model =
						DDMSeparatorField.superclass.getPropertyModel.apply(
							instance,
							arguments
						);

					model.push({
						attributeName: 'style',
						editor: new A.TextAreaCellEditor({
							strings: editorLocalizedStrings,
						}),
						name: 'Style',
					});

					return model;
				},
			},
		});

		const DDMHTMLTextField = A.Component.create({
			ATTRS: {
				dataType: {
					value: 'html',
				},

				fieldNamespace: {
					value: 'ddm',
				},

				indexType: {
					valueFn() {
						return structureFieldIndexEnable() ? 'text' : '';
					},
				},
			},

			EXTENDS: FormBuilderTextField,

			NAME: 'ddm-text-html',

			prototype: {
				getHTML() {
					return TPL_TEXT_HTML;
				},
			},
		});

		const DDMJournalArticleField = A.Component.create({
			ATTRS: {
				dataType: {
					value: 'journal-article',
				},

				fieldNamespace: {
					value: 'ddm',
				},
			},

			EXTENDS: A.FormBuilderField,

			NAME: 'ddm-journal-article',

			prototype: {
				getHTML() {
					return TPL_INPUT_BUTTON;
				},

				getPropertyModel() {
					const instance = this;

					const model =
						DDMJournalArticleField.superclass.getPropertyModel.apply(
							instance,
							arguments
						);

					model.push({
						attributeName: 'style',
						editor: new A.TextAreaCellEditor({
							strings: editorLocalizedStrings,
						}),
						name: 'Style',
					});

					model.forEach((item) => {
						const attributeName = item.attributeName;

						if (attributeName === 'predefinedValue') {
							item.editor = new JournalArticleCellEditor({
								strings: editorLocalizedStrings,
							});

							item.formatter = function (object) {
								const data = object.data;

								let label = STR_BLANK;

								const value = data.value;

								if (value !== STR_BLANK) {
									label =
										'(' +
										'Web\x20Content' +
										')';
								}

								return label;
							};
						}
					});

					return model;
				},
			},
		});

		const DDMLinkToPageField = A.Component.create({
			ATTRS: {
				dataType: {
					value: 'link-to-page',
				},

				fieldNamespace: {
					value: 'ddm',
				},
			},

			EXTENDS: A.FormBuilderField,

			NAME: 'ddm-link-to-page',

			prototype: {
				getHTML() {
					return TPL_INPUT_BUTTON;
				},
			},
		});

		const DDMTextAreaField = A.Component.create({
			ATTRS: {
				indexType: {
					valueFn() {
						return structureFieldIndexEnable() ? 'text' : '';
					},
				},
			},

			EXTENDS: A.FormBuilderTextAreaField,

			NAME: 'textarea',
		});

		const plugins = [
			DDMColorField,
			DDMDateField,
			DDMDecimalField,
			DDMDocumentLibraryField,
			DDMGeolocationField,
			DDMImageField,
			DDMIntegerField,
			DDMJournalArticleField,
			DDMLinkToPageField,
			DDMNumberField,
			DDMParagraphField,
			DDMRadioField,
			DDMSeparatorField,
			DDMHTMLTextField,
			DDMTextAreaField,
		];

		plugins.forEach((item) => {
			FormBuilderTypes[item.OVERRIDE_TYPE || item.NAME] = item;
		});
	},
	'',
	{
		requires: [
			'aui-base',
			'aui-color-picker-popover',
			'aui-url',
			'liferay-item-selector-dialog',
			'liferay-portlet-dynamic-data-mapping',
		],
	}
);

/**
 * SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
 * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
 */

AUI.add(
	'liferay-portlet-dynamic-data-mapping',
	(A) => {
		const AArray = A.Array;
		const Lang = A.Lang;

		const BODY = document.body;

		const instanceOf = A.instanceOf;
		const isArray = Array.isArray;

		const isFormBuilderField = function (value) {
			return value instanceof A.FormBuilderField;
		};

		const isObject = Lang.isObject;
		const isString = Lang.isString;
		const isUndefined = Lang.isUndefined;

		const DEFAULTS_FORM_VALIDATOR = A.config.FormValidator;

		const ICON_ASTERISK_TPL =
			'<label>' +
			'<span class="reference-mark">' +
			Liferay.Util.getLexiconIconTpl('asterisk') +
			'</span>' +
			'</label>';

		const ICON_QUESTION_TPL =
			'<span>' +
			Liferay.Util.getLexiconIconTpl('question-circle-full') +
			'</span>';

		const MAP_HIDDEN_FIELD_ATTRS = {
			DEFAULT: ['readOnly', 'width'],

			checkbox: ['readOnly'],

			separator: [
				'indexType',
				'localizable',
				'predefinedValue',
				'readOnly',
				'required',
			],
		};

		const REGEX_HYPHEN = /[-–—]/i;

		const SETTINGS_TAB_INDEX = 1;

		const STR_BLANK = '';

		const STR_SPACE = ' ';

		const STR_UNDERSCORE = '_';

		DEFAULTS_FORM_VALIDATOR.STRINGS.structureFieldName =
			'Please\x20enter\x20only\x20alphanumeric\x20characters\x2e';

		DEFAULTS_FORM_VALIDATOR.RULES.structureFieldName = function (value) {
			return /^[\w-]+$/.test(value);
		};

		// Updates icons to produce lexicon SVG markup instead of default glyphicon

		A.PropertyBuilderAvailableField.prototype.FIELD_ITEM_TEMPLATE =
			A.PropertyBuilderAvailableField.prototype.FIELD_ITEM_TEMPLATE.replace(
				/<\s*span[^>]*>(.*?)<\s*\/\s*span>/,
				Liferay.Util.getLexiconIconTpl('{iconClass}')
			);

		A.ToolbarRenderer.prototype.TEMPLATES.icon =
			Liferay.Util.getLexiconIconTpl('{cssClass}');

		const LiferayAvailableField = A.Component.create({
			ATTRS: {
				localizationMap: {
					validator: isObject,
					value: {},
				},
				name: {
					validator: isString,
				},
			},

			EXTENDS: A.FormBuilderAvailableField,

			NAME: 'availableField',
		});

		const ReadOnlyFormBuilderSupport = function () {};

		ReadOnlyFormBuilderSupport.ATTRS = {
			readOnly: {
				value: false,
			},
		};

		A.mix(ReadOnlyFormBuilderSupport.prototype, {
			_afterFieldRender(event) {
				const field = event.target;

				if (instanceOf(field, A.FormBuilderField)) {
					const readOnlyAttributes = AArray.map(
						field.getPropertyModel(),
						(item) => {
							return item.attributeName;
						}
					);

					field.set('readOnlyAttributes', readOnlyAttributes);
				}
			},

			_afterRenderReadOnlyFormBuilder() {
				const instance = this;

				instance.tabView.enableTab(1);
				instance.openEditProperties(instance.get('fields').item(0));
				instance.tabView.getTabs().item(0).hide();
			},

			_onMouseOverFieldReadOnlyFormBuilder(event) {
				const field = A.Widget.getByNode(event.currentTarget);

				field.controlsToolbar.hide();

				field
					.get('boundingBox')
					.removeClass('form-builder-field-hover');
			},

			initializer() {
				const instance = this;

				if (instance.get('readOnly')) {
					instance.set('allowRemoveRequiredFields', false);
					instance.set('enableEditing', false);
					instance.translationManager.hide();

					instance.after(
						'render',
						instance._afterRenderReadOnlyFormBuilder
					);

					instance.after('*:render', instance._afterFieldRender);

					instance.dropContainer.delegate(
						'mouseover',
						instance._onMouseOverFieldReadOnlyFormBuilder,
						'.form-builder-field'
					);
				}
			},
		});

		A.LiferayAvailableField = LiferayAvailableField;

		const LiferayFormBuilder = A.Component.create({
			ATTRS: {
				availableFields: {
					validator: isObject,
					valueFn() {
						return LiferayFormBuilder.AVAILABLE_FIELDS.DEFAULT;
					},
				},

				fieldNameEditionDisabled: {
					value: false,
				},

				portletNamespace: {
					value: STR_BLANK,
				},

				portletResourceNamespace: {
					value: STR_BLANK,
				},

				propertyList: {
					value: {
						strings: {
							asc: 'Ascending',

							// eslint-disable-next-line @liferay/no-abbreviations
							desc: 'Descending',
							propertyName: 'Property\x20Name',
							reverseSortBy: Lang.sub(
								'Reverse\x20Sort\x20by\x20\x7b0\x7d',
								['{column}']
							),
							sortBy: Lang.sub(
								'Sort\x20by\x20\x7b0\x7d',
								['{column}']
							),
							value: 'Value',
						},
					},
				},

				strings: {
					value: {
						addNode: 'Add\x20Field',
						button: 'Button',
						buttonType: 'Button\x20Type',
						cancel: 'Cancel',
						deleteFieldsMessage: 'Are\x20you\x20sure\x20you\x20want\x20to\x20delete\x20the\x20selected\x20entries\x3f\x20They\x20will\x20be\x20deleted\x20immediately\x2e',
						duplicateMessage: 'Duplicate',
						editMessage: 'Edit',
						label: 'Field\x20Label',
						large: 'Large',
						localizable: 'Localizable',
						medium: 'Medium',
						multiple: 'Multiple',
						name: 'Name',
						no: 'No',
						options: 'Options',
						predefinedValue:
							'Predefined\x20Value',
						propertyName: 'Property\x20Name',
						required: 'Required',
						requiredDescription: 'Required\x20Description',
						reset: 'Reset',
						save: 'Save',
						settings: 'Settings',
						showLabel: 'Show\x20Label',
						small: 'Small',
						submit: 'Submit',
						tip: 'Tip',
						type: 'Type',
						value: 'Value',
						width: 'Width',
						yes: 'Yes',
					},
				},

				translationManager: {
					validator: isObject,
					value: {},
				},

				validator: {
					setter(val) {
						const config = {
							fieldStrings: {
								name: {
									required: 'This\x20field\x20is\x20required\x2e',
								},
							},
							rules: {
								name: {
									required: true,
									structureFieldName: true,
								},
							},
							...val,
						};

						return config;
					},
					value: {},
				},
			},

			AUGMENTS: [ReadOnlyFormBuilderSupport],

			EXTENDS: A.FormBuilder,

			LOCALIZABLE_FIELD_ATTRS: [
				'label',
				'options',
				'predefinedValue',
				'style',
				'tip',
			],

			NAME: 'liferayformbuilder',

			UNIQUE_FIELD_NAMES_MAP: new A.Map(),

			UNLOCALIZABLE_FIELD_ATTRS: [
				'dataType',
				'fieldNamespace',
				'indexType',
				'localizable',
				'multiple',
				'name',
				'readOnly',
				'repeatable',
				'required',
				'requiredDescription',
				'showLabel',
				'type',
			],

			prototype: {
				_afterEditingLocaleChange(event) {
					const instance = this;

					instance._toggleInputDirection(event.newVal);
				},

				_afterFieldsChange(event) {
					const instance = this;

					const tabs = instance.tabView.getTabs();

					const activeTabIndex = tabs.indexOf(
						instance.tabView.getActiveTab()
					);

					if (activeTabIndex === SETTINGS_TAB_INDEX) {
						instance.editField(event.newVal.item(0));
					}

					this._handleAlertMessages(instance.get('fields'));
				},

				_beforeGetEditor(record, column) {
					if (column.key === 'name') {
						return;
					}

					const instance = this;

					const columnEditor = column.editor;

					const recordEditor = record.get('editor');

					const editor = recordEditor || columnEditor;

					if (instanceOf(editor, A.BaseOptionsCellEditor)) {
						if (editor.get('rendered')) {
							instance._toggleOptionsEditorInputs(editor);
						}
						else {
							editor.after('render', () => {
								instance._toggleOptionsEditorInputs(editor);
							});
						}
					}

					editor.after('render', () => {
						editor.set('visible', true);

						const boundingBox = editor.get('boundingBox');

						if (boundingBox) {
							boundingBox.show();
						}
					});
				},

				_deserializeField(fieldJSON, availableLanguageIds) {
					const instance = this;

					const fields = fieldJSON.fields;

					if (isArray(fields)) {
						fields.forEach((item) => {
							instance._deserializeField(
								item,
								availableLanguageIds
							);
						});
					}

					instance._deserializeFieldLocalizationMap(
						fieldJSON,
						availableLanguageIds
					);
					instance._deserializeFieldLocalizableAttributes(fieldJSON);
				},

				_deserializeFieldLocalizableAttributes(fieldJSON) {
					const instance = this;

					const defaultLocale =
						instance.translationManager.get('defaultLocale');
					const editingLocale =
						instance.translationManager.get('editingLocale');

					LiferayFormBuilder.LOCALIZABLE_FIELD_ATTRS.forEach(
						(item) => {
							const localizedValue = fieldJSON[item];

							if (item !== 'options' && localizedValue) {
								fieldJSON[item] =
									localizedValue[editingLocale] ||
									localizedValue[defaultLocale];
							}
						}
					);
				},

				_deserializeFieldLocalizationMap(
					fieldJSON,
					availableLanguageIds
				) {
					const instance = this;

					availableLanguageIds.forEach((languageId) => {
						fieldJSON.localizationMap =
							fieldJSON.localizationMap || {};
						fieldJSON.localizationMap[languageId] = {};

						LiferayFormBuilder.LOCALIZABLE_FIELD_ATTRS.forEach(
							(attribute) => {
								const attributeMap = fieldJSON[attribute];

								if (attributeMap && attributeMap[languageId]) {
									fieldJSON.localizationMap[languageId][
										attribute
									] = attributeMap[languageId];
								}
							}
						);
					});

					if (fieldJSON.options) {
						instance._deserializeFieldOptionsLocalizationMap(
							fieldJSON,
							availableLanguageIds
						);
					}
				},

				_deserializeFieldOptionsLocalizationMap(
					fieldJSON,
					availableLanguageIds
				) {
					const instance = this;

					let labels;

					const defaultLocale =
						instance.translationManager.get('defaultLocale');
					const editingLocale =
						instance.translationManager.get('editingLocale');

					fieldJSON.options.forEach((item) => {
						labels = item.label;

						item.label =
							labels[editingLocale] || labels[defaultLocale];

						item.localizationMap = {};

						availableLanguageIds.forEach((languageId) => {
							item.localizationMap[languageId] = {
								label: labels[languageId],
							};
						});
					});
				},

				_getGeneratedFieldName(label) {
					const normalizedLabel =
						LiferayFormBuilder.Util.normalizeKey(label);

					let generatedName = normalizedLabel;

					if (
						LiferayFormBuilder.Util.validateFieldName(generatedName)
					) {
						let counter = 1;

						while (
							LiferayFormBuilder.UNIQUE_FIELD_NAMES_MAP.has(
								generatedName
							)
						) {
							generatedName = normalizedLabel + counter++;
						}
					}

					return generatedName;
				},

				_getSerializedFields() {
					const instance = this;

					const fields = [];

					instance.get('fields').each((field) => {
						fields.push(field.serialize());
					});

					return fields;
				},

				_handleAlertMessages(fields) {
					const hasDocumentLibrary = fields.some(
						(field) => field.name === 'ddm-documentlibrary'
					);
					const documentsAndMediaField = document.querySelector(
						'.ddm-documents-and-media-field'
					);
					if (documentsAndMediaField !== null) {
						const isHidden =
							documentsAndMediaField.classList.contains('hide');
						if (hasDocumentLibrary && isHidden) {
							documentsAndMediaField.classList.remove('hide');
						}
						else if (!hasDocumentLibrary) {
							documentsAndMediaField.classList.add('hide');
						}
					}
				},

				_onDataTableRender(event) {
					const instance = this;

					A.on(
						instance._beforeGetEditor,
						event.target,
						'getEditor',
						instance
					);
				},

				_onDefaultLocaleChange(event) {
					const instance = this;

					const fields = instance.get('fields');

					const newVal = event.newVal;

					const translationManager = instance.translationManager;

					const availableLanguageIds =
						translationManager.get('availableLocales');

					if (availableLanguageIds.indexOf(newVal) < 0) {
						const config = {
							fields,
							newVal,
							prevVal: event.prevVal,
						};

						translationManager.addAvailableLocale(newVal);

						instance._updateLocalizationMaps(config);
					}
				},

				_onMouseOutField(event) {
					const instance = this;

					const field = A.Widget.getByNode(event.currentTarget);

					instance._setInvalidDDHandles(field, 'remove');

					LiferayFormBuilder.superclass._onMouseOutField.apply(
						instance,
						arguments
					);
				},

				_onMouseOverField(event) {
					const instance = this;

					const field = A.Widget.getByNode(event.currentTarget);

					instance._setInvalidDDHandles(field, 'add');

					LiferayFormBuilder.superclass._onMouseOverField.apply(
						instance,
						arguments
					);
				},

				_onPropertyModelChange(event) {
					const instance = this;

					const fieldNameEditionDisabled = instance.get(
						'fieldNameEditionDisabled'
					);

					const changed = event.changed;

					const attributeName = event.target.get('attributeName');

					const editingField = instance.editingField;

					const readOnlyAttributes =
						editingField.get('readOnlyAttributes');

					if (
						Object.prototype.hasOwnProperty.call(
							changed,
							'value'
						) &&
						readOnlyAttributes.indexOf('name') === -1
					) {
						if (attributeName === 'name') {
							editingField.set(
								'autoGeneratedName',
								event.autoGeneratedName === true
							);
						}
						else if (
							attributeName === 'label' &&
							editingField.get('autoGeneratedName') &&
							!fieldNameEditionDisabled
						) {
							const translationManager =
								instance.translationManager;

							if (
								translationManager.get('editingLocale') ===
								translationManager.get('defaultLocale')
							) {
								const generatedName =
									instance._getGeneratedFieldName(
										changed.value.newVal
									);

								if (
									LiferayFormBuilder.Util.validateFieldName(
										generatedName
									)
								) {
									const nameModel = instance.propertyList
										.get('data')
										.filter((item) => {
											return (
												item.get('attributeName') ===
												'name'
											);
										});

									if (nameModel.length) {
										nameModel[0].set(
											'value',
											generatedName,
											{
												autoGeneratedName: true,
											}
										);
									}
								}
							}
						}
						else if (editingField.get('type') === 'ddm-image') {
							if (attributeName === 'required') {
								if (editingField.get('requiredDescription')) {
									instance._toggleImageDescriptionAsterisk(
										editingField,
										changed.value.newVal === 'true'
									);
								}

								instance._toggleRequiredDescriptionPropertyModel(
									editingField,
									changed.value.newVal === 'true'
								);
							}
							else if (
								attributeName === 'requiredDescription' &&
								editingField.get('required')
							) {
								instance._toggleImageDescriptionAsterisk(
									editingField,
									changed.value.newVal === 'true'
								);
							}
						}
						else if (
							attributeName === 'multiple' &&
							changed.value.newVal === 'false'
						) {
							editingField.set('multiple', changed.value.newVal);
							editingField.set('predefinedValue', ['']);

							instance.editField(editingField);
						}
					}
				},

				_renderSettings() {
					const instance = this;

					instance._renderPropertyList();

					// Dynamically removes unnecessary icons from editor toolbar buttons

					const defaultGetEditorFn = instance.propertyList.getEditor;

					instance.propertyList.getEditor = function () {
						const editor = defaultGetEditorFn.apply(
							this,
							arguments
						);

						if (editor) {
							const defaultSetToolbarFn = A.bind(
								editor._setToolbar,
								editor
							);

							editor._setToolbar = function (val) {
								const toolbar = defaultSetToolbarFn(val);

								if (toolbar && toolbar.children) {
									toolbar.children = toolbar.children.map(
										(children) => {
											children = children.map((item) => {
												delete item.icon;

												return item;
											});

											return children;
										}
									);
								}

								return toolbar;
							};
						}

						return editor;
					};
				},

				_setAvailableFields(val) {
					const fields = val.map((item) => {
						return instanceOf(item, A.PropertyBuilderAvailableField)
							? item
							: new A.LiferayAvailableField(item);
					});

					fields.sort((a, b) => {
						return A.ArraySort.compare(
							a.get('label'),
							b.get('label')
						);
					});

					return fields;
				},

				_setFields() {
					const instance = this;

					LiferayFormBuilder.UNIQUE_FIELD_NAMES_MAP.clear();

					return LiferayFormBuilder.superclass._setFields.apply(
						instance,
						arguments
					);
				},

				_setFieldsSortableListConfig() {
					const instance = this;

					const config =
						LiferayFormBuilder.superclass._setFieldsSortableListConfig.apply(
							instance,
							arguments
						);

					config.dd.plugins = [
						{
							cfg: {
								constrain: '#main-content',
							},
							fn: A.Plugin.DDConstrained,
						},
						{
							cfg: {
								horizontal: false,
								node: '#main-content',
							},
							fn: A.Plugin.DDNodeScroll,
						},
					];

					return config;
				},

				_setInvalidDDHandles(field, type) {
					const instance = this;

					const methodName = type + 'Invalid';

					instance.eachParentField(field, (parent) => {
						const parentBB = parent.get('boundingBox');

						parentBB.dd[methodName]('#' + parentBB.attr('id'));
					});
				},

				_toggleImageDescriptionAsterisk(field, state) {
					const requiredNode = field
						._getFieldNode()
						.one('.lexicon-icon-asterisk');

					if (requiredNode) {
						requiredNode.toggle(state);
					}
				},

				_toggleInputDirection(locale) {
					const rtl = Liferay.Language.direction[locale] === 'rtl';

					BODY.classList.toggle('form-builder-ltr-inputs', !rtl);
					BODY.classList.toggle('form-builder-rtl-inputs', rtl);
				},

				_toggleOptionsEditorInputs(editor) {
					const instance = this;

					const boundingBox = editor.get('boundingBox');

					if (boundingBox.hasClass('radiocelleditor')) {
						const defaultLocale =
							instance.translationManager.get('defaultLocale');
						const editingLocale =
							instance.translationManager.get('editingLocale');

						const inputs = boundingBox.all(
							'.celleditor-edit-input-value'
						);

						Liferay.Util.toggleDisabled(
							inputs,
							defaultLocale !== editingLocale
						);
					}
				},

				_toggleRequiredDescriptionPropertyModel(field, state) {
					const instance = this;

					const modelList = instance.propertyList.get('data');

					if (state) {
						modelList.add(
							{
								...field.getRequiredDescriptionPropertyModel(),
								value: field.get('requiredDescription'),
							},
							{
								index:
									modelList.indexOf(
										modelList.getById('required')
									) + 1,
							}
						);
					}
					else {
						modelList.remove(
							modelList.getById('requiredDescription')
						);
					}
				},

				_updateLocalizationMaps(config) {
					const instance = this;

					const fields = config.fields;
					const newVal = config.newVal;
					const prevVal = config.prevVal;

					fields._items.forEach((field) => {
						const childFields = field.get('fields');
						const localizationMap = field.get('localizationMap');

						const config = {
							fields: childFields,
							newVal,
							prevVal,
						};

						localizationMap[newVal] = localizationMap[prevVal];

						instance._updateLocalizationMaps(config);
					});
				},

				bindUI() {
					const instance = this;

					LiferayFormBuilder.superclass.bindUI.apply(
						instance,
						arguments
					);

					instance.translationManager.after(
						'defaultLocaleChange',
						instance._onDefaultLocaleChange,
						instance
					);
					instance.translationManager.after(
						'editingLocaleChange',
						instance._afterEditingLocaleChange,
						instance
					);

					instance.on(
						'datatable:render',
						instance._onDataTableRender
					);
					instance.on(
						'drag:drag',
						A.DD.DDM.syncActiveShims,
						A.DD.DDM,
						true
					);
					instance.on(
						'model:change',
						instance._onPropertyModelChange
					);
				},

				createField() {
					const instance = this;

					const field =
						LiferayFormBuilder.superclass.createField.apply(
							instance,
							arguments
						);

					if (field.name === 'ddm-image') {
						if (!field.get('required')) {
							instance._toggleImageDescriptionAsterisk(
								field,
								false
							);

							instance.MAP_HIDDEN_FIELD_ATTRS.DEFAULT.push(
								'requiredDescription'
							);
						}
						else if (field.get('requiredDescription') === false) {
							instance._toggleImageDescriptionAsterisk(
								field,
								false
							);
						}
					}

					// Dynamically updates field toolbar items to produce lexicon svg markup instead of default glyphicon

					field.set(
						'requiredFlagNode',
						A.Node.create(ICON_ASTERISK_TPL)
					);

					field.set('tipFlagNode', A.Node.create(ICON_QUESTION_TPL));

					const defaultGetToolbarItemsFn = A.bind(
						field._getToolbarItems,
						field
					);

					field._getToolbarItems = function () {
						const toolbarItems = defaultGetToolbarItemsFn();

						return (
							toolbarItems &&
							toolbarItems.map((toolbarItem) => {
								return toolbarItem.map((item) => {
									if (item.icon) {
										item.icon = item.icon
											.replace('glyphicon glyphicon-', '')
											.replace('wrench', 'cog');
									}

									return item;
								});
							})
						);
					};

					field.set('strings', instance.get('strings'));

					const fieldHiddenAttributeMap = {
						'checkbox': instance.MAP_HIDDEN_FIELD_ATTRS.checkbox,
						'ddm-separator':
							instance.MAP_HIDDEN_FIELD_ATTRS.separator,
						'default': instance.MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					};

					let hiddenAtributes =
						fieldHiddenAttributeMap[field.get('type')];

					if (!hiddenAtributes) {
						hiddenAtributes = fieldHiddenAttributeMap.default;
					}

					field.set('hiddenAttributes', hiddenAtributes);

					return field;
				},

				deserializeDefinitionFields(content) {
					const instance = this;

					const availableLanguageIds = content.availableLanguageIds;

					const fields = content.fields;

					fields.forEach((fieldJSON) => {
						instance._deserializeField(
							fieldJSON,
							availableLanguageIds
						);
					});

					return fields;
				},

				eachParentField(field, fn) {
					const instance = this;

					let parent = field.get('parent');

					while (isFormBuilderField(parent)) {
						fn.call(instance, parent);

						parent = parent.get('parent');
					}
				},

				getContent() {
					const instance = this;

					const definition = {};

					const translationManager = instance.translationManager;

					definition.availableLanguageIds =
						translationManager.get('availableLocales');
					definition.defaultLanguageId =
						translationManager.get('defaultLocale');

					definition.fields = instance._getSerializedFields();

					return JSON.stringify(definition, null, 4);
				},

				getContentValue() {
					const instance = this;

					return window[
						instance.get('portletResourceNamespace') +
							'getContentValue'
					]();
				},

				initializer() {
					const instance = this;

					instance.MAP_HIDDEN_FIELD_ATTRS = A.clone(
						MAP_HIDDEN_FIELD_ATTRS
					);

					const translationManager = (instance.translationManager =
						new Liferay.TranslationManager(
							instance.get('translationManager')
						));

					instance.after('render', () => {
						translationManager.render();
					});

					instance.after('fieldsChange', instance._afterFieldsChange);

					if (themeDisplay.isStatePopUp()) {
						instance.addTarget(Liferay.Util.getOpener().Liferay);
					}

					instance._toggleInputDirection(
						translationManager.get('defaultLocale')
					);
				},

				plotField(field) {
					const instance = this;

					LiferayFormBuilder.UNIQUE_FIELD_NAMES_MAP.put(
						field.get('name'),
						field
					);

					return LiferayFormBuilder.superclass.plotField.apply(
						instance,
						arguments
					);
				},
			},
		});

		LiferayFormBuilder.Util = {
			getFileEntry(fileJSON, callback) {
				const instance = this;

				fileJSON = instance.parseJSON(fileJSON);

				Liferay.Service(
					'/dlapp/get-file-entry-by-uuid-and-group-id',
					{
						groupId: fileJSON.groupId,
						uuid: fileJSON.uuid,
					},
					callback
				);
			},

			getFileEntryURL(fileEntry) {
				const buffer = [
					themeDisplay.getPathContext(),
					'documents',
					fileEntry.groupId,
					fileEntry.folderId,
					encodeURIComponent(fileEntry.title),
				];

				return buffer.join('/');
			},

			normalizeKey(key) {
				key = key.trim();

				for (let i = 0; i < key.length; i++) {
					const item = key[i];

					if (
						!A.Text.Unicode.test(item, 'L') &&
						!A.Text.Unicode.test(item, 'N') &&
						!A.Text.Unicode.test(item, 'Pd') &&
						item !== STR_UNDERSCORE
					) {
						key = key.replace(item, STR_SPACE);
					}
				}

				key = Lang.String.camelize(key, STR_SPACE);

				return key.replace(/\s+/gi, '');
			},

			normalizeValue(value) {
				if (isUndefined(value)) {
					value = STR_BLANK;
				}

				return value;
			},

			parseJSON(value) {
				let data = {};

				try {
					data = JSON.parse(value);
				}
				catch (error) {}

				return data;
			},

			validateFieldName(fieldName) {
				let valid = true;

				if (REGEX_HYPHEN.test(fieldName)) {
					valid = false;

					return valid;
				}

				for (let i = 0; i < fieldName.length; i++) {
					const item = fieldName[i];

					if (
						!A.Text.Unicode.test(item, 'L') &&
						!A.Text.Unicode.test(item, 'N') &&
						!A.Text.Unicode.test(item, 'Pd') &&
						item !== STR_UNDERSCORE
					) {
						valid = false;

						break;
					}
				}

				return valid;
			},
		};

		LiferayFormBuilder.DEFAULT_ICON_CLASS = 'text';

		const AVAILABLE_FIELDS = {
			DDM_STRUCTURE: [
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.checkbox,
					iconClass: 'check-square',
					label: 'Boolean',
					type: 'checkbox',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'adjust',
					label: 'Color',
					type: 'ddm-color',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'calendar',
					label: 'Date',
					type: 'ddm-date',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'decimal',
					label: 'Decimal',
					type: 'ddm-decimal',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'document-text',
					label: 'Documents\x20and\x20Media',
					type: 'ddm-documentlibrary',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'text',
					label: 'Web\x20Content',
					type: 'ddm-journal-article',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'code',
					label: 'HTML',
					type: 'ddm-text-html',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'globe',
					label: 'Geolocation',
					type: 'ddm-geolocation',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'integer',
					label: 'Integer',
					type: 'ddm-integer',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'link',
					label: 'Link\x20to\x20Page',
					type: 'ddm-link-to-page',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'number',
					label: 'Number',
					type: 'ddm-number',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'radio-button',
					label: 'Radio',
					type: 'radio',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'select',
					label: 'Select',
					type: 'select',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'text',
					label: 'Text',
					type: 'text',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'textbox',
					label: 'Text\x20Box',
					type: 'textarea',
				},
			],

			DDM_TEMPLATE: [
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'paragraph',
					label: 'Paragraph',
					type: 'ddm-paragraph',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'separator',
					label: 'Separator',
					type: 'ddm-separator',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'blogs',
					label: 'Fieldset',
					type: 'fieldset',
				},
			],

			DEFAULT: [
				{
					fieldLabel: 'Button',
					iconClass: 'square-hole',
					label: 'Button',
					type: 'button',
				},
				{
					fieldLabel: 'Checkbox',
					iconClass: 'check-square',
					label: 'Checkbox',
					type: 'checkbox',
				},
				{
					fieldLabel: 'Fieldset',
					iconClass: 'cards',
					label: 'Fieldset',
					type: 'fieldset',
				},
				{
					fieldLabel: 'Text\x20Box',
					iconClass: 'text',
					label: 'Text\x20Box',
					type: 'text',
				},
				{
					fieldLabel: 'Text\x20Area\x20\x28HTML\x29',
					iconClass: 'textbox',
					label: 'Text\x20Area\x20\x28HTML\x29',
					type: 'textarea',
				},
				{
					fieldLabel: 'Radio\x20Buttons',
					iconClass: 'radio',
					label: 'Radio\x20Buttons',
					type: 'radio',
				},
				{
					fieldLabel: 'Select\x20Option',
					iconClass: 'select',
					label: 'Select\x20Option',
					type: 'select',
				},
			],

			WCM_STRUCTURE: [
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
					iconClass: 'picture',
					label: 'Image',
					type: 'ddm-image',
				},
				{
					hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.separator,
					iconClass: 'separator',
					label: 'Separator',
					type: 'ddm-separator',
				},
			],
		};

		AVAILABLE_FIELDS.WCM_STRUCTURE = AVAILABLE_FIELDS.WCM_STRUCTURE.concat(
			AVAILABLE_FIELDS.DDM_STRUCTURE
		);

		LiferayFormBuilder.AVAILABLE_FIELDS = AVAILABLE_FIELDS;

		Liferay.FormBuilder = LiferayFormBuilder;
	},
	'',
	{
		requires: [
			'arraysort',
			'aui-form-builder-deprecated',
			'aui-form-validator',
			'aui-map',
			'aui-text-unicode',
			'json',
			'liferay-menu',
			'liferay-translation-manager',
			'liferay-util-window',
			'text',
		],
	}
);

/**
 * SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
 * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
 */

AUI.add(
	'liferay-portlet-dynamic-data-lists',
	(A) => {
		const AArray = A.Array;

		const DateMath = A.DataType.DateMath;

		const FormBuilder = Liferay.FormBuilder;

		const Lang = A.Lang;

		const EMPTY_FN = A.Lang.emptyFn;

		const STR_EMPTY = '';

		const isArray = Array.isArray;
		const isNumber = Lang.isNumber;

		const SpreadSheet = A.Component.create({
			ATTRS: {
				addRecordURL: {
					validator: Lang.isString,
					value: STR_EMPTY,
				},

				portletNamespace: {
					validator: Lang.isString,
					value: STR_EMPTY,
				},

				recordsetId: {
					validator: isNumber,
					value: 0,
				},

				structure: {
					validator: isArray,
					value: [],
				},

				updateRecordURL: {
					validator: Lang.isString,
					value: STR_EMPTY,
				},
			},

			CSS_PREFIX: 'table',

			DATATYPE_VALIDATOR: {
				double: 'number',
				integer: 'digits',
				long: 'digits',
			},

			EXTENDS: A.DataTable,

			NAME: A.DataTable.Base.NAME,

			TYPE_EDITOR: {
				'checkbox': A.CheckboxCellEditor,
				'ddm-color':
					FormBuilder.CUSTOM_CELL_EDITORS['color-cell-editor'],
				'ddm-date': A.DateCellEditor,
				'ddm-decimal': A.TextCellEditor,
				'ddm-documentlibrary':
					FormBuilder.CUSTOM_CELL_EDITORS[
						'document-library-file-entry-cell-editor'
					],
				'ddm-integer': A.TextCellEditor,
				'ddm-link-to-page':
					FormBuilder.CUSTOM_CELL_EDITORS['link-to-page-cell-editor'],
				'ddm-number': A.TextCellEditor,
				'radio': A.RadioCellEditor,
				'select': A.DropDownCellEditor,
				'text': A.TextCellEditor,
				'textarea': A.TextAreaCellEditor,
			},

			addRecord(
				addRecordURL,
				callback,
				ddmFormValues,
				displayIndex,
				portletNamespace,
				recordsetId
			) {
				const instance = this;

				callback = (callback && A.bind(callback, instance)) || EMPTY_FN;

				// eslint-disable-next-line @liferay/aui/no-io
				A.io.request(addRecordURL, {
					data: Liferay.Util.ns(portletNamespace, {
						ddmFormValues: JSON.stringify(ddmFormValues),
						displayIndex,
						groupId: themeDisplay.getScopeGroupId(),
						recordSetId: recordsetId,
						serviceContext: JSON.stringify({
							scopeGroupId: themeDisplay.getScopeGroupId(),
							userId: themeDisplay.getUserId(),
							workflowAction: Liferay.Workflow.ACTION_PUBLISH,
						}),
					}),
					dataType: 'JSON',
					method: 'POST',
					on: {
						success(data) {
							callback(JSON.parse(data.details[1].response));
						},
					},
				});
			},

			buildDataTableColumns(columns, locale, structure, editable) {
				const instance = this;

				columns.forEach((item) => {
					const dataType = item.dataType;
					let label = item.label;
					const name = item.name;
					const type = item.type;

					item.key = name;

					const EditorClass =
						instance.TYPE_EDITOR[type] || A.TextCellEditor;

					const config = {
						elementName: name,
						strings: {
							cancel: 'Cancel',
							edit: 'Edit',
							save: 'Save',
						},
						validator: {
							rules: {},
						},
					};

					const required = item.required;

					let structureField;

					if (required) {
						label += ' (' + 'Required' + ')';
					}

					label = A.Escape.html(label);

					item.label = label;

					if (type === 'checkbox') {
						config.options = {
							true: 'True',
						};

						config.inputFormatter = function (value) {
							if (Array.isArray(value) && !!value.length) {
								value = value[0];
							}

							let checkedValue = 'false';

							if (value === 'true') {
								checkedValue = value;
							}

							return checkedValue;
						};

						item.formatter = function (object) {
							const data = object.data;

							let value = data[name];

							if (value === 'true') {
								value = 'True';
							}
							else if (value === 'false') {
								value = 'False';
							}

							return value;
						};
					}
					else if (type === 'ddm-date') {
						config.inputFormatter = function (val) {
							return val.map((item) => {
								return A.DataType.Date.format(item);
							});
						};

						config.outputFormatter = function (val) {
							return val.map((item) => {
								let date;

								if (item !== STR_EMPTY) {
									date = A.DataType.Date.parse(item);
								}
								else {
									date = new Date();
								}

								date = DateMath.add(
									date,
									DateMath.MINUTES,
									date.getTimezoneOffset()
								);

								return date;
							});
						};

						item.formatter = function (object) {
							const data = object.data;

							let value = data[name];

							if (isArray(value)) {
								value = value[0];
							}

							return value;
						};
					}
					else if (
						type === 'ddm-decimal' ||
						type === 'ddm-integer' ||
						type === 'ddm-number'
					) {
						config.outputFormatter = function (value) {
							const number = A.DataType.Number.parse(value);

							let numberValue = STR_EMPTY;

							if (isNumber(number)) {
								numberValue = number;
							}

							return numberValue;
						};

						item.formatter = function (object) {
							const data = object.data;

							let value = A.DataType.Number.parse(data[name]);

							if (!isNumber(value)) {
								value = STR_EMPTY;
							}

							return value;
						};
					}
					else if (type === 'ddm-documentlibrary') {
						item.formatter = function (object) {
							const data = object.data;

							let label = STR_EMPTY;
							const value = data[name];

							if (value !== STR_EMPTY) {
								const fileData =
									FormBuilder.Util.parseJSON(value);

								if (fileData.title) {
									label = fileData.title;
								}
							}

							return label;
						};
					}
					else if (type === 'ddm-link-to-page') {
						item.formatter = function (object) {
							const data = object.data;

							let label = STR_EMPTY;
							const value = data[name];

							if (value !== STR_EMPTY) {
								const linkToPageData =
									FormBuilder.Util.parseJSON(value);

								if (linkToPageData.name) {
									label = linkToPageData.name;
								}
							}

							return label;
						};
					}
					else if (type === 'radio') {
						structureField = instance.findStructureFieldByAttribute(
							structure,
							'name',
							name
						);

						config.multiple = false;
						config.options = instance.getCellEditorOptions(
							structureField.options,
							locale
						);
					}
					else if (type === 'select') {
						structureField = instance.findStructureFieldByAttribute(
							structure,
							'name',
							name
						);

						const multiple = A.DataType.Boolean.parse(
							structureField.multiple
						);
						const options = instance.getCellEditorOptions(
							structureField.options,
							locale
						);

						item.formatter = function (object) {
							const data = object.data;

							const label = [];
							const value = data[name];

							if (isArray(value)) {
								value.forEach((item1) => {
									label.push(options[item1]);
								});
							}

							return label.join(', ');
						};

						config.inputFormatter = AArray;
						config.multiple = multiple;
						config.options = options;
					}
					else if (type === 'textarea') {
						item.allowHTML = true;

						item.formatter = function (object) {
							const data = object.data;

							const value = data[name];

							if (!value) {
								return value;
							}

							return value.split('\n').join('<br>');
						};
					}

					const validatorRuleName =
						instance.DATATYPE_VALIDATOR[dataType];

					const validatorRules = config.validator.rules;

					validatorRules[name] = A.mix(
						{
							required,
						},
						validatorRules[name]
					);

					if (validatorRuleName) {
						validatorRules[name][validatorRuleName] = true;
					}

					if (editable && item.editable) {
						item.editor = new EditorClass(config);
					}
				});

				return columns;
			},

			buildEmptyRecords(num, keys) {
				const instance = this;

				const emptyRows = [];

				for (let i = 0; i < num; i++) {
					emptyRows.push(instance.getRecordModel(keys));
				}

				return emptyRows;
			},

			findStructureFieldByAttribute(
				fieldsArray,
				attributeName,
				attributeValue
			) {
				const instance = this;

				let structureField;

				AArray.some(fieldsArray, (item) => {
					const nestedFieldsArray = item.fields;

					if (item[attributeName] === attributeValue) {
						structureField = item;
					}
					else if (nestedFieldsArray) {
						structureField = instance.findStructureFieldByAttribute(
							nestedFieldsArray,
							attributeName,
							attributeValue
						);
					}

					return structureField !== undefined;
				});

				return structureField;
			},

			getCellEditorOptions(options, locale) {
				const normalized = {};

				options.forEach((item) => {
					normalized[item.value] = item.label;

					const localizationMap = item.localizationMap;

					if (localizationMap[locale]) {
						normalized[item.value] = localizationMap[locale].label;
					}
				});

				return normalized;
			},

			getRecordModel(keys) {
				const recordModel = {};

				keys.forEach((item) => {
					recordModel[item] = STR_EMPTY;
				});

				return recordModel;
			},

			prototype: {
				_afterActiveCellIndexChange() {
					const instance = this;

					const activeCell = instance.get('activeCell');
					const boundingBox = instance.get('boundingBox');

					const scrollableElement =
						boundingBox.one('.table-x-scroller');

					const tableHighlightBorder =
						instance.highlight.get('activeBorderWidth')[0];

					const activeCellWidth =
						activeCell.outerWidth() + tableHighlightBorder;
					const scrollableWidth = scrollableElement.outerWidth();

					const activeCellOffsetLeft = activeCell.get('offsetLeft');
					const scrollLeft = scrollableElement.get('scrollLeft');

					const activeCellOffsetRight =
						activeCellOffsetLeft + activeCellWidth;

					let scrollTo = scrollLeft;

					if (scrollLeft + scrollableWidth < activeCellOffsetRight) {
						scrollTo = activeCellOffsetRight - scrollableWidth;
					}
					else if (activeCellOffsetLeft < scrollLeft) {
						scrollTo = activeCellOffsetLeft;
					}

					scrollableElement.set('scrollLeft', scrollTo);
				},

				_afterSelectionKey(event) {
					const instance = this;

					const activeCell = instance.get('activeCell');

					const alignNode = event.alignNode || activeCell;

					const column = instance.getColumn(alignNode);

					if (
						activeCell &&
						event.keyCode === 13 &&
						column.type !== 'textarea'
					) {
						instance._onEditCell(activeCell);
					}
				},

				_normalizeFieldData(item, record, normalized, field) {
					const instance = this;

					const type = item.type;
					let value = record.get(item.name);

					if (type === 'ddm-link-to-page') {
						value = FormBuilder.Util.parseJSON(value);

						delete value.name;

						value = JSON.stringify(value);
					}
					else if (type === 'select') {
						if (!isArray(value)) {
							value = AArray(value);
						}

						value = JSON.stringify(value);
					}

					const fieldValue = {
						instanceId: instance._randomString(8),
						name: item.name,
					};

					if (item.localizable) {
						fieldValue['value'] = {
							[themeDisplay.getLanguageId()]: value.toString(),
						};
					}
					else {
						fieldValue['value'] = value;
					}

					normalized['fieldValues'].push(fieldValue);

					if (isArray(item.fields) && !!item.fields.length) {
						fieldValue['nestedFieldValues'] = [];

						item.fields.forEach((nestedItem) => {
							instance._normalizeFieldData(
								nestedItem,
								record,
								normalized,
								fieldValue
							);
						});
					}

					if (field) {
						field['nestedFieldValues'].push(fieldValue);
					}
				},

				_normalizeRecordData(record) {
					const instance = this;

					const structure = instance.get('structure');

					const normalized = {
						availableLanguageIds: [themeDisplay.getLanguageId()],
						defaultLanguageId: themeDisplay.getLanguageId(),
						fieldValues: [],
					};

					structure.forEach((item) => {
						instance._normalizeFieldData(item, record, normalized);

						if (item.fields) {
							item.fields.forEach((nestedField) =>
								instance._normalizeFieldData(
									nestedField,
									record,
									normalized
								)
							);
						}
					});

					delete normalized.displayIndex;
					delete normalized.recordId;

					return normalized;
				},

				_onDataChange(event) {
					const instance = this;

					instance._setDataStableSort(event.newVal);
				},

				_onEditCell(event) {
					const instance = this;

					SpreadSheet.superclass._onEditCell.apply(
						instance,
						arguments
					);

					const activeCell = instance.get('activeCell');

					const alignNode = event.alignNode || activeCell;

					const column = instance.getColumn(alignNode);
					const record = instance.getRecord(alignNode);

					const data = instance.get('data');
					const portletNamespace = instance.get('portletNamespace');
					const recordsetId = instance.get('recordsetId');
					const structure = instance.get('structure');

					const editor = instance.getEditor(record, column);

					if (editor) {
						editor.setAttrs({
							data,
							portletNamespace,
							record,
							recordsetId,
							structure,
							zIndex: Liferay.zIndex.OVERLAY,
						});
					}
				},

				_onRecordUpdate(event) {
					const instance = this;

					if (
						!Object.prototype.hasOwnProperty.call(
							event.changed,
							'recordId'
						)
					) {
						const data = instance.get('data');
						const recordsetId = instance.get('recordsetId');

						const record = event.target;

						const recordId = record.get('recordId');

						const fieldsMap = instance._normalizeRecordData(record);

						const recordIndex = data.indexOf(record);

						if (recordId > 0) {
							SpreadSheet.updateRecord(
								recordId,
								recordIndex,
								recordsetId,
								fieldsMap,
								false,
								instance.get('portletNamespace'),
								instance.get('updateRecordURL')
							);
						}
						else {
							SpreadSheet.addRecord(
								instance.get('addRecordURL'),
								(json) => {
									if (json.recordId > 0) {
										record.set('recordId', json.recordId, {
											silent: true,
										});
									}
								},
								fieldsMap,
								recordIndex,
								instance.get('portletNamespace'),
								recordsetId
							);
						}
					}
				},

				_randomString(length) {
					const random = Math.random();

					const randomString = random.toString(36);

					return randomString.substring(length);
				},

				_setDataStableSort(data) {
					data.sort = function (options) {
						if (this.comparator) {
							options = options || {};

							const models = this._items.concat();

							A.ArraySort.stableSort(
								models,
								A.bind(this._sort, this)
							);

							const facade = {
								...options,
								models,
								src: 'sort',
							};

							if (options.silent) {
								this._defResetFn(facade);
							}
							else {
								this.fire('reset', facade);
							}
						}

						return this;
					};
				},

				addEmptyRows(num) {
					const instance = this;

					const columns = instance.get('columns');
					const data = instance.get('data');

					const keys = columns.map((item) => {
						return item.key;
					});

					data.add(SpreadSheet.buildEmptyRecords(num, keys));
				},

				initializer() {
					const instance = this;

					instance._setDataStableSort(instance.get('data'));

					instance.set('scrollable', true);

					instance.on('dataChange', instance._onDataChange);
					instance.on('model:change', instance._onRecordUpdate);
				},

				updateMinDisplayRows(minDisplayRows, callback) {
					const instance = this;

					callback =
						(callback && A.bind(callback, instance)) || EMPTY_FN;

					const recordsetId = instance.get('recordsetId');

					Liferay.Service(
						'/ddl.ddlrecordset/update-min-display-rows',
						{
							minDisplayRows,
							recordSetId: recordsetId,
							serviceContext: JSON.stringify({
								scopeGroupId: themeDisplay.getScopeGroupId(),
								userId: themeDisplay.getUserId(),
							}),
						},
						callback
					);
				},
			},

			updateRecord(
				recordId,
				displayIndex,
				recordSetId,
				ddmFormValues,
				majorVersion,
				portletNamespace,
				updateRecordURL,
				callback
			) {
				const instance = this;

				callback = (callback && A.bind(callback, instance)) || EMPTY_FN;

				// eslint-disable-next-line @liferay/aui/no-io
				A.io.request(updateRecordURL, {
					data: Liferay.Util.ns(portletNamespace, {
						ddmFormValues: JSON.stringify(ddmFormValues),
						displayIndex,
						majorVersion,
						recordId,
						recordSetId,
					}),
					dataType: 'JSON',
					method: 'POST',
					on: {
						success() {
							callback();
						},
					},
				});
			},
		});

		Liferay.SpreadSheet = SpreadSheet;

		const DDLUtil = {
			openPreviewDialog(content) {
				const instance = this;

				let previewDialog = instance.previewDialog;

				if (!previewDialog) {
					previewDialog = Liferay.Util.Window.getWindow({
						dialog: {
							bodyContent: content,
						},
						title: 'Preview',
					});

					instance.previewDialog = previewDialog;
				}
				else {
					previewDialog.show();

					previewDialog.set('bodyContent', content);
				}
			},

			previewDialog: null,
		};

		Liferay.DDLUtil = DDLUtil;
	},
	'',
	{
		requires: [
			'aui-arraysort',
			'aui-datatable',
			'aui-io-deprecated',
			'datatable-sort',
			'json',
			'liferay-portlet-dynamic-data-mapping-custom-fields',
			'liferay-util-window',
		],
	}
);

