text: import { LightningElement, api } from "lwc";
  
  const MINIMAL_SEARCH_TERM_LENGTH = 2; // Min number of chars required to search
  const SEARCH_DELAY = 300; // Wait 300 ms after user stops typing then, peform search
  
  export default class Lookup extends LightningElement {
  	@api label;
  	@api required;
  	@api placeholder = "";
  	@api isMultiEntry = false;
  	@api errors = [];
  	@api scrollAfterNItems;
  	@api customKey;
  
  	searchTerm = "";
  	searchResults = [];
  	hasFocus = false;
  	loading = false;
  	isDirty = false;
  	isPreview = false;
  
  	cleanSearchTerm;
  	blurTimeout;
  	searchThrottlingTimeout;
  	curSelection = [];
  
  	// EXPOSED FUNCTIONS
  	@api
  	set selection(initialSelection) {
  		this.curSelection = Array.isArray(initialSelection) ? initialSelection : [initialSelection];
  		this.isDirty = false;
  	}
  	get selection() {
  		return this.curSelection;
  	}
  
  	@api
  	setSearchResults(results) {
  		// Reset the spinner
  		this.loading = false;
  		// Clone results before modifying them to avoid Locker restriction
  		const resultsLocal = JSON.parse(JSON.stringify(results));
  		// Format results
  		this.searchResults = resultsLocal.map((result) => {
  			// Clone and complete search result if icon is missing
  			if (this.searchTerm.length > 0 || this.isPreview) {
  				const regex = new RegExp(`(${this.searchTerm})`, "gi");
  				result.titleFormatted = result.title ? result.title.replace(regex, "<strong>$1</strong>") : result.title;
  				result.subtitleFormatted = result.subtitle ? result.subtitle.replace(regex, "<strong>$1</strong>") : result.subtitle;
  			}
  			if (typeof result.icon === "undefined") {
  				const { id, sObjectType, title, subtitle } = result;
  				return {
  					id,
  					sObjectType,
  					icon: "standard:default",
  					title,
  					subtitle
  				};
  			}
  			return result;
  		});
  	}
  
  	@api
  	getSelection() {
  		return this.curSelection;
  	}
  
  	@api
  	getkey() {
  		return this.customKey;
  	}
  
  	// INTERNAL FUNCTIONS
  
  	updateSearchTerm(newSearchTerm) {
  		this.searchTerm = newSearchTerm;
  
  		// Compare clean new search term with current one and abort if identical
  		const newCleanSearchTerm = newSearchTerm.trim().replace(/\*/g, "").toLowerCase();
  		if (this.cleanSearchTerm === newCleanSearchTerm) {
  			return;
  		}
  
  		// Save clean search term
  		this.cleanSearchTerm = newCleanSearchTerm;
  
  		// Ignore search terms that are too small
  		if (newCleanSearchTerm.length < MINIMAL_SEARCH_TERM_LENGTH) {
  			this.searchResults = [];
  			return;
  		}
  
  		// Apply search throttling (prevents search if user is still typing)
  		if (this.searchThrottlingTimeout) {
  			clearTimeout(this.searchThrottlingTimeout);
  		}
  		// eslint-disable-next-line @lwc/lwc/no-async-operation
  		this.searchThrottlingTimeout = setTimeout(() => {
  			// Send search event if search term is long enougth
  			if (this.cleanSearchTerm.length >= MINIMAL_SEARCH_TERM_LENGTH) {
  				// Display spinner until results are returned
  				this.loading = true;
  
  				const searchEvent = new CustomEvent("search", {
  					detail: {
  						searchTerm: this.cleanSearchTerm,
  						selectedIds: this.curSelection.map((element) => element.id)
  					}
  				});
  				this.dispatchEvent(searchEvent);
  			}
  			this.searchThrottlingTimeout = null;
  		}, SEARCH_DELAY);
  	}
  
  	isSelectionAllowed() {
  		if (this.isMultiEntry) {
  			return true;
  		}
  		return !this.hasSelection();
  	}
  
  	hasResults() {
  		return this.searchResults.length > 0;
  	}
  
  	hasSelection() {
  		return this.curSelection.length > 0;
  	}
  
  	// EVENT HANDLING
  
  	handleInput(event) {
  		// Prevent action if selection is not allowed
  		if (!this.isSelectionAllowed()) {
  			return;
  		}
  		this.isPreview = false;
  		this.updateSearchTerm(event.target.value);
  	}
  
  	handleResultClick(event) {
  		const recordId = event.currentTarget.dataset.recordid;
  
  		// Save selection
  		let selectedItem = this.searchResults.filter((result) => result.id === recordId);
  		if (selectedItem.length === 0) {
  			return;
  		}
  		selectedItem = selectedItem[0];
  		const newSelection = [...this.curSelection];
  		newSelection.push(selectedItem);
  		this.curSelection = newSelection;
  		this.isDirty = true;
  
  		// Reset search
  		this.searchTerm = "";
  		this.searchResults = [];
  		this.isPreview = false;
  
  		// Notify parent components that selection has changed
  		this.dispatchEvent(new CustomEvent("selectionchange"));
  	}
  
  	handleComboboxClick() {
  		// Hide combobox immediatly
  		if (this.blurTimeout) {
  			window.clearTimeout(this.blurTimeout);
  		}
  		this.hasFocus = false;
  	}
  
  	handleFocus() {
  		// Prevent action if selection is not allowed
  		if (!this.isSelectionAllowed()) {
  			return;
  		}
  		this.hasFocus = true;
  	}
  
  	handleBlur() {
  		// Prevent action if selection is not allowed
  		if (!this.isSelectionAllowed()) {
  			return;
  		}
  		// Delay hiding combobox so that we can capture selected result
  		// eslint-disable-next-line @lwc/lwc/no-async-operation
  		this.blurTimeout = window.setTimeout(() => {
  			this.hasFocus = false;
  			this.blurTimeout = null;
  			this.isPreview = !this.isPreview;
  		}, 300);
  	}
  
  	handleRemoveSelectedItem(event) {
  		const recordId = event.currentTarget.name;
  		this.curSelection = this.curSelection.filter((item) => item.id !== recordId);
  		this.isDirty = true;
  		// Notify parent components that selection has changed
  		this.dispatchEvent(new CustomEvent("selectionchange"));
  	}
  
  	handleClearSelection() {
  		this.curSelection = [];
  		this.isDirty = true;
  		// Notify parent components that selection has changed
  		this.dispatchEvent(new CustomEvent("selectionchange"));
  	}
  
  	handleClickPreview() {
  		// Prevent action if selection is not allowed
  		if (!this.isSelectionAllowed()) {
  			return;
  		}
  
  		this.searchTerm = "";
  
  		// Save clean search term
  		this.cleanSearchTerm = "";
  
  		this.isPreview = true;
  
  		// Apply search throttling (prevents search if user is still typing)
  		if (this.searchThrottlingTimeout) {
  			clearTimeout(this.searchThrottlingTimeout);
  		}
  		// eslint-disable-next-line @lwc/lwc/no-async-operation
  		this.searchThrottlingTimeout = setTimeout(() => {
  			// Send search event if search term is long enougth
  			// Display spinner until results are returned
  			this.loading = true;
  
  			const clickpreview = new CustomEvent("clickpreview", {
  				detail: {
  					searchTerm: this.cleanSearchTerm,
  					selectedIds: this.selection.map((element) => element.id)
  				}
  			});
  			this.dispatchEvent(clickpreview);
  			this.searchThrottlingTimeout = null;
  		}, 1);
  	}
  
  	// STYLE EXPRESSIONS
  
  	get getContainerClass() {
  		let css = "slds-combobox_container slds-has-inline-listbox ";
  		if (this.hasFocus && this.hasResults()) {
  			css += "slds-has-input-focus ";
  		}
  		if (this.errors.length > 0) {
  			css += "has-custom-error";
  		}
  		return css;
  	}
  
  	get getDropdownClass() {
  		let css = "slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click ";
  		if ((this.hasFocus && this.cleanSearchTerm && this.cleanSearchTerm.length >= MINIMAL_SEARCH_TERM_LENGTH) || this.isPreview) {
  			css += "slds-is-open";
  		}
  		return css;
  	}
  
  	get getInputClass() {
  		let css = "slds-input slds-combobox__input has-custom-height ";
  		if (this.errors.length > 0 || (this.isDirty && this.required && !this.hasSelection())) {
  			css += "has-custom-error ";
  		}
  		if (!this.isMultiEntry) {
  			css += "slds-combobox__input-value " + (this.hasSelection() ? "has-custom-border" : "");
  		}
  		return css;
  	}
  
  	get getComboboxClass() {
  		let css = "slds-combobox__form-element slds-input-has-icon ";
  		if (this.isMultiEntry) {
  			css += "slds-input-has-icon_right";
  		} else {
  			css += this.hasSelection() ? "slds-input-has-icon_left-right" : "slds-input-has-icon_right";
  		}
  		return css;
  	}
  
  	get getSearchIconClass() {
  		let css = "slds-input__icon slds-input__icon_right ";
  		if (!this.isMultiEntry) {
  			css += this.hasSelection() ? "slds-hide" : "";
  		}
  		return css;
  	}
  
  	get getClearSelectionButtonClass() {
  		return "slds-button slds-button_icon slds-input__icon slds-input__icon_right " + (this.hasSelection() ? "" : "slds-hide");
  	}
  
  	get getSelectIconName() {
  		return this.hasSelection() ? this.curSelection[0].icon : "standard:default";
  	}
  
  	get getSelectIconClass() {
  		return "slds-combobox__input-entity-icon " + (this.hasSelection() ? "" : "slds-hide");
  	}
  
  	get getInputValue() {
  		if (this.isMultiEntry) {
  			return this.searchTerm;
  		}
  		return this.hasSelection() ? this.curSelection[0].title : this.searchTerm;
  	}
  
  	get getInputTitle() {
  		if (this.isMultiEntry) {
  			return "";
  		}
  
  		return this.hasSelection() ? this.curSelection[0].title : "";
  	}
  
  	get getListboxClass() {
  		return "slds-listbox slds-listbox_vertical slds-dropdown slds-dropdown_fluid " + (this.scrollAfterNItems ? "slds-dropdown_length-with-icon-" + this.scrollAfterNItems : "");
  	}
  
  	get isInputReadonly() {
  		if (this.isMultiEntry) {
  			return false;
  		}
  		return this.hasSelection();
  	}
  
  	get isExpanded() {
  		return this.hasResults();
  	}
  }
  