// eslint-disable-next-line @typescript-eslint/no-unused-vars
namespace OSFramework.OSUI.Patterns.Dropdown.ServerSide {
	// eslint-disable-next-line @typescript-eslint/naming-convention
	export class OSUIDropdownServerSide
		extends Patterns.AbstractParent<
			OSUIDropdownServerSideConfig,
			Patterns.DropdownServerSideItem.IDropdownServerSideItem
		>
		implements IDropdownServerSide
	{
		// Store the HTML element for the DropdownBalloonContainer
		private _balloonContainerElement: HTMLElement;
		// Store the Balloon Element
		private _balloonElem: HTMLElement;
		// Store the Balloon Class instance
		private _balloonFeature: Feature.Balloon.IBalloon;
		// Store all the focusable elements inside footer if it's the case!
		private _balloonFocusableElemsInFooter: HTMLElement[];
		// Store the HTML element for the DropdownBalloonFooter
		private _balloonFooterElement: HTMLElement;
		// Store the BalloonOptions container AriaLabel text
		private _balloonOptionsAriaLabel: string;
		// Store the HTML element for the Dropdown otpions
		private _balloonOptionsWrapperElement: HTMLElement;
		// Store the HTML element for the Search input at Dropdown Balloon
		private _balloonSearchInputElement: HTMLElement;
		// Store the HTML element for the DropdownBalloonSearch
		private _balloonSearchInputWrapperElement: HTMLElement;
		// Store a Flag property that will help dealing with the focus state at the close moment
		private _closeDynamically = false;
		// Custom Balloon Event
		private _eventBalloonOnToggle: GlobalCallbacks.Generic;
		// Click On Body
		private _eventOnBodyClick: GlobalCallbacks.Generic;
		// Click Event
		private _eventOnClick: GlobalCallbacks.Generic;
		private _eventOnClickInputSearch: GlobalCallbacks.Generic;
		// OnSearchInputBlur Event
		private _eventOnSearchInputBlur: GlobalCallbacks.Generic;
		// OnSearchInputFocus Event
		private _eventOnSearchInputFocus: GlobalCallbacks.Generic;
		// OnTouchMove Event at the balloon wrapper
		private _eventOnTouchMove: GlobalCallbacks.Generic;
		// Keyboard Key Press Event
		private _eventOnkeyboardPress: GlobalCallbacks.Generic;
		// Store the Element State, by default is closed!
		private _isOpen = false;
		// Platform OnClose Callback
		private _platformEventOnToggleCallback: GlobalCallbacks.OSGeneric;
		// Store the HTML element for the Dropdown Select Wrapper
		private _selectValuesWrapper: HTMLElement;
		// Store the SelectValuesWrapper AriaLabel text
		private _selectValuesWrapperAriaLabel: string;
		// Store the Balloon options to pass to the Balloon Class
		public balloonOptions: Feature.Balloon.BalloonOptions;

		constructor(uniqueId: string, configs: JSON) {
			super(uniqueId, new OSUIDropdownServerSideConfig(configs));
		}

		// Add error message container with a given text
		private _addErrorMessage(text: string): void {
			const errorMessageElement = Helper.Dom.ClassSelector(
				this.selfElement.parentElement,
				Enum.CssClass.ErrorMessage
			);

			// Check if the element already exist!
			if (errorMessageElement === undefined) {
				// Create the wrapper container
				const textContainer = document.createElement(GlobalEnum.HTMLElement.Div);
				textContainer.classList.add(Enum.CssClass.ErrorMessage);
				textContainer.innerHTML = text;

				this.selfElement.parentElement.appendChild(textContainer);
			}
		}

		// Method to handle the custom BalloonOnToggle callback
		private _balloonOnToggleCallback(_args: string, e: CustomEvent): void {
			// If the balloon closed is the one from this pattern, toggle the isOpen
			if (e.detail.balloonElem === this._balloonElem && e.detail.isOpen !== this._isOpen) {
				this._close(true);
			}
		}

		// Close the Balloon
		private _close(isFromBalloonEvent = false): void {
			if (this._isOpen === false) {
				return;
			}

			if (!isFromBalloonEvent) {
				this._balloonFeature.close();
			}

			// Update status property
			this._isOpen = false;

			// Update the touchMove when pattern is open!
			this._touchMove();
			// Update pattern status!
			this._updatePatternState();
			// Remove event listeners
			this._unsetEvents(true);
		}

		// Method that will return HasNoImplementation Error Info
		private _hasNoImplementation(): string {
			throw new Error(
				`${ErrorCodes.Dropdown.HasNoImplementation.code}: ${ErrorCodes.Dropdown.HasNoImplementation.message}`
			);
		}

		// Method to move Balloon Options Wrapper to outside of the pattern context
		private _moveBalloonOptionsWrapper(): void {
			// Check if BalloonOptions should be moved outside of the pattern context
			if (this._shouldBalloonOptionsBeMoved()) {
				// Get the content element where to move the BalloonOptionsWrapper
				const contentElem = Helper.Dom.ClassSelector(document, GlobalEnum.CssClassElements.Content);
				// Move the DropdownServerSide ballon element to the content element
				Helper.Dom.Move(this._balloonElem, contentElem);
				// Add a custom css selector in order to style it at this new context
				OSFramework.OSUI.Helper.Dom.Styles.AddClass(this._balloonElem, Enum.CssClass.HasBeenMovedToContent);
			}
		}

		// Close when click outside of pattern
		private _onBodyClick(eventName: string, event: PointerEvent): void {
			if (this._isOpen === false) {
				return;
			}

			this._closeDynamically = true;

			// Check if balloon can be closed
			// If it's phone, always close, as it is on popup mode
			// Also prevent closing when clicking at an element inside search icon placeholder
			const canClose =
				Helper.DeviceInfo.IsPhone &&
				(event.target as HTMLElement).closest(`.${Enum.CssClass.BalloonSearchIcon}`) === null;

			if (canClose) {
				this._close();
			}
		}

		// A11y keyboard keys
		private _onKeyboardPressed(event: KeyboardEvent): void {
			event.stopPropagation();

			// Check which element has been key pressed!
			switch (event.target) {
				// Check if the SelectValuesWrapper container has been pressed!
				case this._selectValuesWrapper:
					// If Escape Key
					if (event.key === GlobalEnum.Keycodes.Escape) {
						this._close();
					}

					// If Enter or Space Keys and Single Option
					if (
						event.key === GlobalEnum.Keycodes.Enter ||
						event.key === GlobalEnum.Keycodes.Space ||
						event.key === GlobalEnum.Keycodes.ArrowUp ||
						event.key === GlobalEnum.Keycodes.ArrowDown ||
						event.key === GlobalEnum.Keycodes.Home
					) {
						// Trigger the click in order to be captured also by DOM (body onClick) in order to close other Dropdowns in page if they are open!
						this._selectValuesWrapper.click();
					}

					break;

				// Check if the Balloon Options container has been pressed!
				case this._balloonOptionsWrapperElement:
					// If Escape Key
					if (event.key === GlobalEnum.Keycodes.Escape) {
						this._close();
					}

					// If ArrowDown Key
					if (event.key === GlobalEnum.Keycodes.ArrowUp) {
						// Check if search input exist
						if (this._balloonSearchInputElement) {
							this._balloonSearchInputElement.focus();
						}
					} else if (event.key === GlobalEnum.Keycodes.ArrowDown) {
						// If ArrowDown Key
						// Focus the first option item!
						event.preventDefault();
						this.getChildByIndex(0).setFocus();
						// Check if Dropdown should only allow single option selected
						if (this.configs.AllowMultipleSelection === false) {
							// Set also the first option item as IsSelected!
							this.getChildByIndex(0).toggleSelected();
						}
					}
					break;

				// Check if the input Search has been pressed!
				case this._balloonSearchInputElement:
					// If Escape Key
					if (event.key === GlobalEnum.Keycodes.Escape) {
						this._close();
					}
					break;
			}
		}

		// Also Used to manage the balloon height accordingly keyboard is in use due to the way iOS deal with it!
		private _onSearchInputBlur(): void {
			Helper.Dom.Styles.RemoveClass(this._balloonElem, Enum.CssClass.SearchInputIsFocused);
		}

		// Used to set a stopPropagation when click at search input
		private _onSearchInputClicked(event: MouseEvent): void {
			event.stopPropagation();
		}

		// Used to manage the balloon height accordingly keyboard is in use due to the way iOS deal with it!
		private _onSearchInputFocus(): void {
			Helper.Dom.Styles.AddClass(this._balloonElem, Enum.CssClass.SearchInputIsFocused);
		}

		// Used to apply the logic when user click to open the Dropdown
		private _onSelectValuesWrapperClicked(): void {
			// Ensure that dropdown can open or close
			this._isOpen ? this._close() : this._open();
		}

		// Manage the OnTouchMove action
		private _onTouchMove(event: TouchEvent): void {
			if (event.target === this._balloonElem) {
				event.preventDefault();
			}
		}

		// Open the Balloon
		private _open(): void {
			if (this._isOpen) {
				return;
			}
			this._balloonFeature.open(true);
			this._closeDynamically = false;
			this._isOpen = true;

			// Update the touchMove when pattern is open!
			this._touchMove();

			// Add the isVisible class to body
			Helper.Dom.Styles.AddClass(document.body, Enum.CssClass.IsVisible);
			this._updatePatternState();

			// Add event listeners
			Helper.AsyncInvocation(this._setUpEvents.bind(this));
		}

		// Method to deal with the click at a DropdpownOptionItem
		private _optionItemHasBeenClicked(optionItemId: string): void {
			const clickedItem = this.getChild(optionItemId);

			// Check if the given OptionId exist at optionsList
			if (clickedItem) {
				// Check if Dropdown should only allow single option selected
				if (this.configs.AllowMultipleSelection) {
					// Udpate the Option Item selected State!
					clickedItem.toggleSelected();
				}

				// Check if Dropdown should only allow single option selected
				if (this.configs.AllowMultipleSelection === false) {
					// Close the Dropdown!
					this._close();

					if (clickedItem.IsSelected === false) {
						clickedItem.toggleSelected();
					}
				}
			} else {
				throw new Error(
					`${ErrorCodes.Dropdown.FailOptionItemClicked}: The ${GlobalEnum.PatternName.DropdownServerSideItem} under uniqueId: '${optionItemId}' does not exist as an OptionItem from ${GlobalEnum.PatternName.Dropdown} with Id: ${this.widgetId}.`
				);
			}
		}

		// Method used to deal with the keyPressed navigation between DropdownOptionItems
		private _optionItemKeyPressed(optionItemId: string): void {
			// Get the optionItem reference based on the given Id
			const optionItem = this.getChild(optionItemId);

			// Check if the given OptionId exist at optionsList
			if (optionItem !== undefined) {
				// Get the option item index position
				const getOptionItemIndex = this.getChildIndex(optionItemId);

				// Ensure that code wont run if key was not defined!
				if (optionItem.keyboardTriggeredKey === undefined) {
					return;
				}

				// Check which Keyboard key has been pressed
				switch (optionItem.keyboardTriggeredKey) {
					// If Enter or Space Keys trigger as a click event!
					case GlobalEnum.Keycodes.Enter:
					case GlobalEnum.Keycodes.Space:
						// Act like option item has been clicked!
						this._optionItemHasBeenClicked(optionItemId);
						break;

					// ArrowUp
					case GlobalEnum.Keycodes.ArrowUp:
						// Check If focused item is not the first one!
						if (getOptionItemIndex > 0) {
							this._updateOptionItemFocusStateOnKeyPress(optionItem, getOptionItemIndex - 1);
						}
						break;

					// ArrowDown
					case GlobalEnum.Keycodes.ArrowDown:
						if (getOptionItemIndex < this.getChildItems().length - 1) {
							this._updateOptionItemFocusStateOnKeyPress(optionItem, getOptionItemIndex + 1);
						}
						break;

					// Shift + Tab
					case GlobalEnum.Keycodes.ShiftTab:
						// Check if search input exist
						if (this._balloonSearchInputElement) {
							this._balloonSearchInputElement.focus();
						}
						break;

					// Tab
					case GlobalEnum.Keycodes.Tab:
						// Check if there are focusable elements inside footer
						if (this._balloonFocusableElemsInFooter.length > 0) {
							// Set focus the the first one
							this._balloonFocusableElemsInFooter[0].focus();
						}
						break;

					// Escape
					case GlobalEnum.Keycodes.Escape:
						// Close Dropdown
						this._close();
						break;
				}
			} else {
				throw new Error(
					`${ErrorCodes.Dropdown.FailOptionItemKeyPressed}: The ${GlobalEnum.PatternName.DropdownServerSideItem} under uniqueId: '${optionItemId}' does not exist as an OptionItem from ${GlobalEnum.PatternName.Dropdown} with Id: ${this.widgetId}.`
				);
			}
		}

		// Method to call the Balloon Class
		private _setBalloonFeature(): void {
			this.setBalloonOptions();

			this._balloonFeature = new OSFramework.OSUI.Feature.Balloon.Balloon<IDropdownServerSide>(
				this,
				this._balloonElem,
				this.balloonOptions
			);

			// Call the method to move the Balloon Options Wrapper
			OSFramework.OSUI.Helper.AsyncInvocation(this._moveBalloonOptionsWrapper.bind(this));
		}

		// Method used to add CSS classes to pattern elements
		private _setCssClasses(): void {
			// If search input exist add a class to the balloon
			if (this._balloonSearchInputElement === undefined) {
				// Needed to style the balloon height once at phone
				Helper.Dom.Styles.AddClass(this._balloonElem, Enum.CssClass.BalloonHasNotSearchInput);
			}
		}

		// Method to set the initial options on screen load
		private _setInitialOptions(): void {
			if (this.configs.IsDisabled) {
				this.disable();
			}
		}

		// Method used to store a given DropdownOption into optionItems list, it's triggered by DropdownServerSideItem
		private _setNewOptionItem(optionItem: Patterns.DropdownServerSideItem.DropdownServerSideItem): void {
			// Check if the given OptionId has been already added
			if (this.getChild(optionItem.uniqueId)) {
				throw new Error(
					`${ErrorCodes.Dropdown.FailSetNewOptionItem}: There is already a ${GlobalEnum.PatternName.DropdownServerSideItem} under Id: '${optionItem.widgetId}' added to ${GlobalEnum.PatternName.Dropdown} with uniqueId: ${this.uniqueId}.`
				);
			} else {
				// Store DropDownOption Child Item
				this.setChild(optionItem);
			}
		}

		// Set Pattern Events
		private _setUpEvents(): void {
			// Add OnClick Event to the SelectValuesWrapper
			this._selectValuesWrapper.addEventListener(GlobalEnum.HTMLEvent.Click, this._eventOnClick);
			// Add KeyDown Event to the SelectValuesWrapper (A11y - stuff)
			this._selectValuesWrapper.addEventListener(GlobalEnum.HTMLEvent.keyDown, this._eventOnkeyboardPress);

			if (this.isBuilt) {
				// Add KeyDown Event to the BalloonContent (OptionsWrapper) (A11y - stuff)
				this._balloonOptionsWrapperElement.addEventListener(
					GlobalEnum.HTMLEvent.keyDown,
					this._eventOnkeyboardPress
				);
				// If search input exist (A11y - stuff)
				if (this._balloonSearchInputElement) {
					this._balloonSearchInputElement.addEventListener(
						GlobalEnum.HTMLEvent.Click,
						this._eventOnClickInputSearch
					);

					// Add keyPress event in order to capture Escape key
					this._balloonSearchInputElement.addEventListener(
						GlobalEnum.HTMLEvent.keyDown,
						this._eventOnkeyboardPress
					);
					// Add BlurEvent in order to manage the Balloon Height when at iOS due to keyboard is in use
					this._balloonSearchInputElement.addEventListener(
						GlobalEnum.HTMLEvent.Blur,
						this._eventOnSearchInputBlur
					);
					// Add FocusEvent in order to manage the Balloon Height when at iOS due to keyboard is in use
					this._balloonSearchInputElement.addEventListener(
						GlobalEnum.HTMLEvent.Focus,
						this._eventOnSearchInputFocus
					);
				}

				// Add the BodyClick callback that will be used Close open Dropdown!
				Event.DOMEvents.Listeners.GlobalListenerManager.Instance.addHandler(
					Event.DOMEvents.Listeners.Type.BodyOnClick,
					this._eventOnBodyClick
				);

				Event.DOMEvents.Listeners.GlobalListenerManager.Instance.addHandler(
					Event.DOMEvents.Listeners.Type.BalloonOnToggle,
					this._eventBalloonOnToggle
				);
			}
		}

		// Method that will check if the BalloonOptionsWrapper should be moved outside of the pattern context
		private _shouldBalloonOptionsBeMoved(): boolean {
			/* NOTE:
				- When inside BottomSheet the BalloonOptionsWrapper should be moved to the content wrapper
				due to position issues related with fixed position of the balloon against BottomSheet fixed position.
				More info at Release Note: ROU-11549
			 */
			// Check if the DropdownServerSide is inside a BottomSheet, Notification, or Sidebar
			if (
				Helper.DeviceInfo.IsPhone &&
				(this.selfElement.closest(Enum.InsidePattern.BottomSheet) ||
					this.selfElement.closest(Enum.InsidePattern.Notification) ||
					this.selfElement.closest(Enum.InsidePattern.Sidebar))
			) {
				return true;
			} else {
				return false;
			}
		}

		// Method used to manage the onTouchMove when we're at mobile devices in order to block the window scroll!
		// This is an improvement specially to iOS since otherwise it will be able to scroll the pattern when keyboard is open!
		private _touchMove(): void {
			// Check if the used browser has TouchMove event and if it's an iOS device
			if (Helper.DeviceInfo.IsIos && 'ontouchmove' in window) {
				if (this._isOpen) {
					this._balloonElem.addEventListener(GlobalEnum.HTMLEvent.TouchMove, this._eventOnTouchMove);
				} else {
					this._balloonElem.removeEventListener(GlobalEnum.HTMLEvent.TouchMove, this._eventOnTouchMove);
				}
			}
		}

		// Mehod used to trigger the _platformEventOnToggleCallback callback!
		private _triggerToogleCalbackEvent(): void {
			this.triggerPlatformEventCallback(this._platformEventOnToggleCallback, this._isOpen);
		}

		// Remove Pattern Events
		private _unsetEvents(isUpdate = false): void {
			if (isUpdate === false) {
				this._selectValuesWrapper.removeEventListener(GlobalEnum.HTMLEvent.Click, this._eventOnClick);
				this._selectValuesWrapper.removeEventListener(GlobalEnum.HTMLEvent.keyDown, this._eventOnkeyboardPress);
			}

			this._balloonOptionsWrapperElement.removeEventListener(
				GlobalEnum.HTMLEvent.keyDown,
				this._eventOnkeyboardPress
			);
			Event.DOMEvents.Listeners.GlobalListenerManager.Instance.removeHandler(
				Event.DOMEvents.Listeners.Type.BalloonOnToggle,
				this._eventBalloonOnToggle
			);

			Event.DOMEvents.Listeners.GlobalListenerManager.Instance.removeHandler(
				Event.DOMEvents.Listeners.Type.BodyOnClick,
				this._eventOnBodyClick
			);
			if (this._balloonSearchInputElement) {
				this._balloonSearchInputElement.removeEventListener(
					GlobalEnum.HTMLEvent.Click,
					this._eventOnClickInputSearch
				);
				this._balloonSearchInputElement.removeEventListener(
					GlobalEnum.HTMLEvent.keyDown,
					this._eventOnkeyboardPress
				);
				this._balloonSearchInputElement.removeEventListener(
					GlobalEnum.HTMLEvent.Blur,
					this._eventOnSearchInputBlur
				);
				this._balloonSearchInputElement.removeEventListener(
					GlobalEnum.HTMLEvent.Focus,
					this._eventOnSearchInputFocus
				);
			}
		}

		// Method used to remove a given DropdownOption from optionItems list, it's triggered by DropdownServerSideItem
		private _unsetNewOptionItem(optionItemId: string): void {
			// Check if the given OptionId exist at optionsList
			if (this.getChild(optionItemId)) {
				// Remove item
				this.unsetChild(optionItemId);
			} else {
				throw new Error(
					`${ErrorCodes.Dropdown.FailUnsetNewOptionItem}: The ${GlobalEnum.PatternName.DropdownServerSideItem} under uniqueId: '${optionItemId}' does not exist as an OptionItem from ${GlobalEnum.PatternName.Dropdown} with Id: ${this.widgetId}.`
				);
			}
		}

		// Method that will be used to set/unset the TabIndex to the DropdownBallon elements according it's opened/closed
		private _updateBalloonAccessibilityElements(): void {
			const tabIndexValue = this._isOpen
				? Constants.A11YAttributes.States.TabIndexShow
				: Constants.A11YAttributes.States.TabIndexHidden;

			// If there is the Search input
			if (this._balloonSearchInputElement !== undefined) {
				Helper.A11Y.TabIndex(this._balloonSearchInputElement, tabIndexValue);
				Helper.A11Y.AriaHidden(
					this._balloonSearchInputElement,
					(tabIndexValue === Constants.A11YAttributes.States.TabIndexHidden).toString()
				);
			}

			// If there are items inside Ballon Footer
			if (this._balloonFocusableElemsInFooter.length > 0) {
				// Will handle the tabindex value of the elements inside pattern
				Helper.A11Y.SetElementsTabIndex(this._isOpen, this._balloonFocusableElemsInFooter);
			}

			// Ballon Options Wrapper
			Helper.A11Y.TabIndex(this._balloonOptionsWrapperElement, tabIndexValue);

			// If there are items inside Ballon Footer
			if (this._balloonFocusableElemsInFooter.length > 0) {
				// Will handle the tabindex value of the elements inside pattern
				Helper.A11Y.SetElementsTabIndex(this._isOpen, this._balloonFocusableElemsInFooter);
			}

			// Update FocusHTML elements attributes
			if (this._isOpen) {
				// Ballon Options Wrapper
				Helper.A11Y.AriaHiddenFalse(this._balloonOptionsWrapperElement);
			} else {
				// Remove a11y selector in order to grant it will be updated each time Balloon gets open
				Helper.Dom.Styles.RemoveClass(this._balloonElem, Constants.HasAccessibilityClass);
				// Ballon Options Wrapper
				Helper.A11Y.AriaHiddenTrue(this._balloonOptionsWrapperElement);
			}
		}

		// Method to (un)set option item focus statue
		private _updateOptionItemFocusStateOnKeyPress(
			optionItem: Patterns.DropdownServerSideItem.IDropdownServerSideItem,
			itemIndex: number
		): void {
			// Check if Dropdown should only allow single option selected!
			if (this.configs.AllowMultipleSelection === false) {
				// Unset IsSelected to the previous Item
				optionItem.toggleSelected(false);
				// Set IsSelected to the next item
				this.getChildByIndex(itemIndex).toggleSelected();
			}

			// Set Blur to the current one!
			optionItem.setBlur();
			// Set Focus to the prev/next one!
			this.getChildByIndex(itemIndex).setFocus();
		}

		// Method that will update the pattern state
		private _updatePatternState(): void {
			// Update the TabIndex for the items inside Balloon
			this._updateBalloonAccessibilityElements();

			// If balloon will open
			if (this._isOpen) {
				// Add IsOpened Class!
				Helper.Dom.Styles.AddClass(this._balloonElem, Enum.CssClass.IsOpened);

				// Check if inputSearch exist
				if (this._balloonSearchInputElement) {
					this._balloonSearchInputElement.focus();
				} else {
					this._balloonOptionsWrapperElement.focus();
				}
			} else {
				// Remove IsOpened Class
				Helper.Dom.Styles.RemoveClass(this._balloonElem, Enum.CssClass.IsOpened);

				// Check if the close will be done by logic instead of user interaction
				if (this._closeDynamically === false) {
					// Set focus to the base element
					this._selectValuesWrapper.focus();
				}
			}

			// Trigger the toggle callback event
			this._triggerToogleCalbackEvent();
		}

		/**
		 * Add the Accessibility Attributes values
		 *
		 * @protected
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		protected setA11YProperties(): void {
			// Update Tabindex Ballon elements
			this._updateBalloonAccessibilityElements();

			// Enabled TabIndex to the SelectValuesWrapper
			Helper.A11Y.TabIndexTrue(this._selectValuesWrapper);
			// Set SelectValuesWrapper with button as a role
			Helper.A11Y.RoleButton(this._selectValuesWrapper);
			// Set SelectValuesWrapper with aria-haspopup='listbox'
			Helper.A11Y.AriaHasPopup(this._selectValuesWrapper, Constants.A11YAttributes.Aria.Haspopup.value.Listbox);
			// Set balloon option items container properties
			Helper.A11Y.RoleListbox(this._balloonOptionsWrapperElement);
			Helper.A11Y.TabIndexFalse(this._balloonOptionsWrapperElement);
			Helper.A11Y.AriaHiddenTrue(this._balloonOptionsWrapperElement);
			// Check if the Dropdown allow multiselect
			if (this.configs.AllowMultipleSelection) {
				// Set the aria-multiselectable attribute to the options wrapper element
				Helper.A11Y.MultiselectableTrue(this._balloonOptionsWrapperElement);
			}
			// Set Aria Label for the SelectValuesWrapper
			this.setSelectAriaLabel();
			// Set Aria Label for the BalloonOptionsWrapper
			this.setBalloonOptionsAriaLabel();
		}

		/**
		 * Method to set the calbacks
		 *
		 * @protected
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		protected setCallbacks(): void {
			this._eventOnBodyClick = this._onBodyClick.bind(this);
			this._eventBalloonOnToggle = this._balloonOnToggleCallback.bind(this);
			this._eventOnClick = this._onSelectValuesWrapperClicked.bind(this);
			this._eventOnClickInputSearch = this._onSearchInputClicked.bind(this);
			this._eventOnkeyboardPress = this._onKeyboardPressed.bind(this);
			this._eventOnSearchInputBlur = this._onSearchInputBlur.bind(this);
			this._eventOnSearchInputFocus = this._onSearchInputFocus.bind(this);
			this._eventOnTouchMove = this._onTouchMove.bind(this);
		}

		/**
		 * Method to set the html elements used
		 *
		 * @protected
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		protected setHtmlElements(): void {
			this._balloonFooterElement = Helper.Dom.ClassSelector(this.selfElement, Enum.CssClass.BalloonFooter);
			this._balloonFocusableElemsInFooter = Helper.Dom.TagSelectorAll(
				this._balloonFooterElement,
				Constants.FocusableElems
			);
			this._balloonSearchInputWrapperElement = Helper.Dom.ClassSelector(
				this.selfElement,
				Enum.CssClass.BalloonSearch
			);
			this._balloonSearchInputElement = Helper.Dom.TagSelector(
				this._balloonSearchInputWrapperElement,
				GlobalEnum.HTMLElement.Input
			);
			this._balloonContainerElement = Helper.Dom.ClassSelector(this.selfElement, Enum.CssClass.BalloonContainer);
			this._balloonElem = Helper.Dom.ClassSelector(this.selfElement, Enum.CssClass.Balloon);
			this._balloonOptionsWrapperElement = Helper.Dom.ClassSelector(
				this._balloonElem,
				Enum.CssClass.BalloonContent
			);
			this._selectValuesWrapper = Helper.Dom.ClassSelector(this.selfElement, Enum.CssClass.SelectValuesWrapper);
		}

		/**
		 * Unset callbacks that has been assigned to the element
		 *
		 * @protected
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		protected unsetCallbacks(): void {
			this._eventOnBodyClick = undefined;
			this._eventBalloonOnToggle = undefined;
			this._eventOnClick = undefined;
			this._eventOnClickInputSearch = undefined;
			this._eventOnkeyboardPress = undefined;
			this._eventOnSearchInputBlur = undefined;
			this._eventOnSearchInputFocus = undefined;
			this._eventOnTouchMove = undefined;
			this._platformEventOnToggleCallback = undefined;
		}

		/**
		 * Method to unset the html elements used
		 *
		 * @protected
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		protected unsetHtmlElements(): void {
			// unset the local properties
			this._balloonContainerElement = undefined;
			this._balloonFocusableElemsInFooter = [];
			this._balloonFooterElement = undefined;
			this._balloonOptionsWrapperElement = undefined;
			this._balloonSearchInputElement = undefined;
			this._balloonSearchInputWrapperElement = undefined;
			this._balloonElem = undefined;
			this._selectValuesWrapper = undefined;
		}

		/**
		 * Method used to be notified by a given dropdownOptionId about a given action and act accordingly
		 *
		 * @param childId Dropdown Option Item Id to be stored
		 * @param notifiedTo {Enum.ChildNotifyActionType} triggered notification type
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public beNotifiedByChild(
			childItem: Patterns.DropdownServerSideItem.DropdownServerSideItem,
			notifiedTo: Enum.ChildNotifyActionType
		): void {
			switch (notifiedTo) {
				case Enum.ChildNotifyActionType.Add:
					this._setNewOptionItem(childItem);
					break;
				case Enum.ChildNotifyActionType.Click:
					this._optionItemHasBeenClicked(childItem.uniqueId);
					break;
				case Enum.ChildNotifyActionType.KeyPressed:
					this._optionItemKeyPressed(childItem.uniqueId);
					break;
				case Enum.ChildNotifyActionType.Removed:
					this._unsetNewOptionItem(childItem.uniqueId);
					break;
				default:
					throw new Error(
						`${ErrorCodes.Dropdown.FailToSetOptionItemAction}: There no exist a '${notifiedTo}' notification type.`
					);
			}
		}

		/**
		 * Method to build the DropdownServerSide
		 *
		 * @memberof OSUIDropdownServerSide
		 */
		public build(): void {
			super.build();
			this.setCallbacks();
			this.setHtmlElements();
			this.setA11YProperties();
			this._setUpEvents();
			this._setCssClasses();
			this._setInitialOptions();
			this._setBalloonFeature();
			this.finishBuild();
		}

		/**
		 * Update property value from a given property name at OnParametersChange
		 *
		 * @param {string} propertyName the name of the property that will be changed
		 * @param {unknown} propertyValue the new value that should be assigned to the given property name
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public changeProperty(propertyName: string, propertyValue: unknown): void {
			super.changeProperty(propertyName, propertyValue);

			if (this.isBuilt) {
				switch (propertyName) {
					case Enum.Properties.IsDisabled:
						propertyValue ? this.disable() : this.enable();
						break;
				}
			}
		}

		/**
		 * Method that will check for all Selected OptionItems and Unselect them
		 *
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public clear(): void {
			// Get all Selected Items
			const selectedOptions = this.getChildItems().filter((item) => item.IsSelected);
			// Go through all the selected option items
			for (const optionItem of selectedOptions) {
				// Unselect it!
				optionItem.toggleSelected(false);
			}
		}

		/**
		 * Method used to close the Dropdown
		 *
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public close(): void {
			// SetTimeout is needed in order to ensure there is no conflit between OnClickBody and a button click that trigger this method.
			OSFramework.OSUI.Helper.AsyncInvocation(this._close.bind(this));
		}

		/**
		 * Set pattern with a disable status
		 *
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public disable(): void {
			// Assign disabled status.
			Helper.Dom.Attribute.Set(this.selfElement, GlobalEnum.HTMLAttributes.Disabled, '');
			// Assign IsDisabled class
			Helper.Dom.Styles.AddClass(this.selfElement, Enum.CssClass.IsDisabled);
			// Assign tabindex value on values wrapper
			Helper.A11Y.TabIndexFalse(this._selectValuesWrapper);
		}

		/**
		 * Destroy the Dropdown.
		 *
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public dispose(): void {
			this._unsetEvents();
			this._balloonFeature?.dispose();
			this.unsetCallbacks();
			this.unsetHtmlElements();
			super.dispose();
		}

		/**
		 * Remove disable status from
		 *
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public enable(): void {
			// Remove disabled status.
			Helper.Dom.Attribute.Remove(this.selfElement, GlobalEnum.HTMLAttributes.Disabled);
			// Remove IsDisabled class
			Helper.Dom.Styles.RemoveClass(this.selfElement, Enum.CssClass.IsDisabled);
			// Assign tabindex value on values wrapper
			Helper.A11Y.TabIndexTrue(this._selectValuesWrapper);
		}

		/**
		 * This method has no implementation on this context.
		 *
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public getSelectedValues(): string {
			return this._hasNoImplementation();
		}

		/**
		 * Method used to open the Dropdown
		 *
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public open(): void {
			// SetTimeout is needed in order to ensure there is no conflit between OnClickBody and a button click that trigger this method.
			OSFramework.OSUI.Helper.AsyncInvocation(this._open.bind(this));
		}

		/**
		 * Method used to register the provider callback
		 *
		 * @param {string} eventName Event name that will be assigned
		 * @param {GlobalCallbacks.OSGeneric} callback Function name that will be passed as a callback function to the event above
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public registerCallback(eventName: string, callback: GlobalCallbacks.OSGeneric): void {
			switch (eventName) {
				case Enum.Events.OnToggle:
					if (this._platformEventOnToggleCallback === undefined) {
						this._platformEventOnToggleCallback = callback;
					}
					break;

				default:
					super.registerCallback(eventName, callback);
			}
		}

		/**
		 * Method to set the Balloon options
		 *
		 * @param {Feature.Balloon.BalloonOptions} [balloonOptions]
		 * @memberof OSUIDropdownServerSide
		 */
		public setBalloonOptions(balloonOptions?: Feature.Balloon.BalloonOptions): void {
			if (balloonOptions !== undefined) {
				this.balloonOptions = balloonOptions;
			} else {
				// Set focus options to pass to the Balloon feature
				const _focusOptions = {
					elemToFocusOnOpen: this._selectValuesWrapper,
					useFocus: true,
					focusTrapParams: {
						focusBottomCallback: this.close.bind(this),
						focusTargetElement: this._balloonContainerElement,
						focusTopCallback: this.close.bind(this),
					},
				};

				this.balloonOptions = {
					alignment: GlobalEnum.FloatingAlignment.Start,
					anchorElem: this._selectValuesWrapper,
					allowedPlacements: [GlobalEnum.FloatingPosition.TopStart, GlobalEnum.FloatingPosition.BottomStart],
					position: GlobalEnum.FloatingPosition.Auto,
					shape: GlobalEnum.ShapeTypes.SoftRounded,
					focusOptions: _focusOptions,
					useTriggerWidth: true,
				};
			}
		}

		/**
		 * Method used to set the balloon AriaLabelText that will be applied to the balloon options container element
		 *
		 * @param value Text to be added
		 */
		public setBalloonOptionsAriaLabel(value?: string): void {
			this._balloonOptionsAriaLabel = value === undefined ? this.configs.balloonOptionsArialabel : value;
			Helper.A11Y.AriaLabel(this._balloonOptionsWrapperElement, this._balloonOptionsAriaLabel);
		}

		/**
		 * This method has no implementation on this context.
		 *
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public setProviderConfigs(): string {
			return this._hasNoImplementation();
		}

		/**
		 * This method has no implementation on this context.
		 *
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public setProviderEvent(): string {
			return this._hasNoImplementation();
		}

		/**
		 * Method used to set the AriaLabel text that will be applied to the SelectValuesWrapper "input" element
		 *
		 * @param value Text to be added
		 */
		public setSelectAriaLabel(value?: string): void {
			this._selectValuesWrapperAriaLabel =
				value === undefined ? this.configs.selectValuesWrapperAriaLabel : value;
			Helper.A11Y.AriaLabel(this._selectValuesWrapper, this._selectValuesWrapperAriaLabel);
		}

		/**
		 * This method has no implementation on this context.
		 *
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public unsetProviderEvent(): string {
			return this._hasNoImplementation();
		}

		/**
		 * Set the validation status, and also pass the message to show
		 *
		 * @param {boolean} Set if the dropdown is valid or not
		 * @param {string} Text message to be added
		 * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide
		 */
		public validation(isValid: boolean, validationMessage: string): void {
			if (isValid === false) {
				Helper.Dom.Styles.AddClass(this.selfElement, Enum.CssClass.NotValid);
				this._addErrorMessage(validationMessage);
			} else {
				Helper.Dom.Styles.RemoveClass(this.selfElement, Enum.CssClass.NotValid);

				const errorMessageElement = Helper.Dom.ClassSelector(
					this.selfElement.parentElement,
					Enum.CssClass.ErrorMessage
				);

				// If error message has been added already, remove it!
				if (errorMessageElement) {
					errorMessageElement.remove();
				}
			}
		}
	}
}
