/**
 * 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',
		],
	}
);

