import { html, nothing } from 'lit';
import { FlowRdfFormControl } from '../../internal/flow-ui-rdf-element.mjs';
import { property, state, query } from 'lit/decorators.js';
import { FormControlController } from '../../internal/form.mjs';
import { debounce, isStringFull } from '@emuanalytics/flow-rdf-core/utils';
import { watch } from '../../internal/watch.mjs';
import { boolean } from '../../internal/boolean.mjs';
import { classMap } from 'lit-html/directives/class-map.js';
import { HasSlotController } from '../../internal/slot.mjs';
import { LocalizeController as LocalizeController$1 } from '../../internal/localize.mjs';
import { ifDefined } from 'lit-html/directives/if-defined.js';
import FlowUiIcon from '../icon/icon.component.mjs';
import FlowUiPopup from '../popup/popup.component.mjs';
import styles from './autocomplete.styles.mjs';
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.mjs';
import { stopAnimations, animateTo } from '../../internal/animate.mjs';
import { waitForEvent } from '../../internal/event.mjs';

var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
    if (decorator = decorators[i])
      result = (kind ? decorator(target, key, result) : decorator(result)) || result;
  if (kind && result)
    __defProp(target, key, result);
  return result;
};
class FlowUiAutocomplete extends FlowRdfFormControl {
  constructor() {
    super(...arguments);
    this.hasSlotController = new HasSlotController(this, "help-text", "label");
    this.localize = new LocalizeController$1(this);
    /** @internal Last time when the mousedown event was fired */
    this.mouseDownAt = 0;
    this.pendingChangeEvent = false;
    this.pendingInputEvent = false;
    this.hasOptions = false;
    this.formControlController = new FormControlController(this, {
      assumeInteractionOn: ["flow-ui-blur", "flow-ui-input"]
    });
    this.size = "medium";
    this.filled = false;
    this.pill = false;
    this.label = "";
    this.helpText = "";
    this.placeholder = "";
    this.disabled = false;
    this.required = false;
    this.inline = false;
    this.divider = false;
    this.hoist = false;
    this.placement = "bottom";
    this.value = "";
    this.displayWith = (value) => value;
    this.debounceDelay = 0;
    this.clearable = false;
    this.clearOnFocus = false;
    this.inputText = "";
    this.open = false;
    this.selectedOption = null;
    this.currentOption = null;
    this.hasFocus = false;
    /** @internal Handle mouse and keyboard events in the document, outside the control. */
    this.handleDocumentEvent = (event) => {
      const path = event.composedPath();
      if (this && !path.includes(this) && this.hasFocus) {
        this.blur();
      }
    };
  }
  connectedCallback() {
    super.connectedCallback();
    this.disabled = boolean(this.disabled, true);
    this.filled = boolean(this.filled, true);
    this.pill = boolean(this.pill, true);
    this.required = boolean(this.required, true);
    this.inline = boolean(this.inline, true);
    this.divider = boolean(this.divider, true);
    this.clearable = boolean(this.clearable, true);
    this.hoist = boolean(this.hoist, true);
    this.clearOnFocus = boolean(this.clearOnFocus, true);
  }
  /** Gets the associated form, if one exists. */
  getForm() {
    return this.formControlController.getForm();
  }
  /** Gets the validity state object.*/
  get validity() {
    return this.input.validity;
  }
  /** Gets the validation message. */
  get validationMessage() {
    return this.input.validationMessage;
  }
  /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */
  checkValidity() {
    return this.input.checkValidity();
  }
  /** Checks for validity and shows the browser's validation message if the control is invalid. */
  reportValidity() {
    return this.input.reportValidity();
  }
  /** Sets a custom validation message. Pass an empty string to restore validity. */
  setCustomValidity(_) {
    this.formControlController.updateValidity();
  }
  /** @internal Gets an array of all <flow-ui-option> elements */
  getAllOptions() {
    return [...this.querySelectorAll("flow-ui-option")];
  }
  /** @internal Gets the first <flow-ui-option> element */
  getFirstOption() {
    return this.querySelector("flow-ui-option");
  }
  /**
   * @internal
   * Un-highlights currently highlighted option
   * and highlights the given option
   */
  setCurrentOption(option) {
    if (this.currentOption) {
      this.currentOption.current = false;
    }
    option.current = true;
    option.focus();
    this.currentOption = option;
  }
  /**
   * @internal
   * Unselects currently selected option
   * and selects the given option
   */
  setSelectedOption(option) {
    if (this.selectedOption) {
      this.selectedOption.selected = false;
    }
    if (!option) {
      this.selectedOption = null;
      return;
    }
    option.selected = true;
    this.selectedOption = option;
  }
  /**
   * @internal
   * Sets the value of the control.
   * Should trigger {@link handleValueChange} when value is set.
   * By default, it will emit a `flow-ui-change` event.
   *
   * @param value - The value to set.
   * @param emitChangeEvent - A change event will be emitted if true.
   */
  setValue(value, options) {
    this.pendingChangeEvent = options?.emitChangeEvent ?? false;
    this.pendingInputEvent = options?.emitInputEvent ?? false;
    this.value = value;
  }
  updateDebounceDelay() {
    this.debouncedInput = debounce(
      (inputText) => {
        this.inputText = inputText;
        this.emit("flow-ui-input");
      },
      this.debounceDelay,
      { leading: false, trailing: true, context: this }
    );
  }
  /** @internal Handle the `flow-ui-input` event from the input box. */
  handleInput(event) {
    event.stopPropagation();
    this.debouncedInput(this.input.value);
  }
  /** @internal Handle the `flow-ui-change` event from the input box */
  handleInputChange(event) {
    event.stopPropagation();
  }
  /** @internal Handle `flow-ui-focus` event from the input box. */
  handleInputFocus(event) {
    event.stopPropagation();
    this.focus();
  }
  /** @internal Handle `flow-ui-focus` event from the input box. */
  handleInputBlur(event) {
    event.stopPropagation();
  }
  handleClearClick(event) {
    event.stopPropagation();
    event.preventDefault();
  }
  /** @internal Handle the `mousedown` event from the clear button */
  async handleClearMouseDown(event) {
    event.stopPropagation();
    event.preventDefault();
    this.setValue("", { emitInputEvent: true, emitChangeEvent: true });
    await waitForEvent(this, "flow-ui-input");
    this.hasOptions = false;
    this.input.focus({ preventScroll: true });
  }
  /** @internal Handle option selection when an option is clicked on */
  async handleListboxClick(event) {
    if (Date.now() - this.mouseDownAt < 200) {
      return;
    }
    const target = event.target;
    const option = target.closest("flow-ui-option");
    if (option && !option.disabled) {
      this.setValue(option.value, { emitChangeEvent: true });
      this.input.focus();
      await waitForEvent(this, "flow-ui-change");
      this.blur();
    }
  }
  /** @internal */
  async handleKeyDown(event) {
    if (event.key === "Escape" && this.open) {
      event.preventDefault();
      event.stopPropagation();
      this.hide();
      this.input.focus({ preventScroll: true });
    }
    if (["ArrowUp", "ArrowDown"].includes(event.key) && this.open) {
      const allOptions = this.getAllOptions();
      const currentIndex = this.currentOption ? allOptions.indexOf(this.currentOption) : 0;
      let newIndex = Math.max(0, currentIndex);
      event.preventDefault();
      if (!this.open) {
        this.show();
        if (this.currentOption) {
          return;
        }
      }
      switch (event.key) {
        case "ArrowDown": {
          newIndex = currentIndex + 1;
          if (newIndex >= allOptions.length) {
            newIndex = 0;
          }
          break;
        }
        case "ArrowUp": {
          newIndex = currentIndex - 1;
          if (newIndex < 0) {
            newIndex = allOptions.length - 1;
          }
          break;
        }
      }
      this.setCurrentOption(allOptions[newIndex]);
    }
    if (event.key === "Enter" && this.open) {
      event.preventDefault();
      event.stopPropagation();
      if (this.currentOption) {
        this.setSelectedOption(this.currentOption);
        this.setValue(this.currentOption.value, { emitChangeEvent: true });
      }
      await waitForEvent(this, "flow-ui-change");
      this.blur();
    }
  }
  /** @internal Add event listeners to the document when the dropdown is opened. */
  addListenersForDropdown() {
    document.addEventListener("mousedown", this.handleDocumentEvent);
    document.addEventListener("focusin", this.handleDocumentEvent);
    document.addEventListener("keydown", this.handleDocumentEvent);
  }
  /** @internal Remove event listeners from the document when the dropdown is closed. */
  removeListenersForDropdown() {
    document.removeEventListener("mousedown", this.handleDocumentEvent);
    document.removeEventListener("focusin", this.handleDocumentEvent);
    document.addEventListener("keydown", this.handleDocumentEvent);
  }
  /** @internal Handles changes in the default slot */
  handleDefaultSlotChange() {
    const options = this.getAllOptions();
    this.hasOptions = options.length > 0;
    this.toggleListbox();
    const firstOption = this.getFirstOption();
    if (firstOption) {
      this.setCurrentOption(firstOption);
    }
    this.setSelectedOption(null);
    for (const option of options) {
      if (option.value === this.value) {
        this.setSelectedOption(option);
        this.setCurrentOption(option);
        break;
      }
    }
  }
  /** @internal Toggles listbox based on focus and options in the default slot. */
  toggleListbox() {
    if (this.hasFocus && this.hasOptions) {
      this.show();
    } else {
      this.hide();
    }
  }
  /**
   * @internal
   * Syncs the {@link displayValue} by applying {@link displayWith} based on the current {@link value}.
   */
  async syncDisplayValue() {
    await this.updateComplete.then(async () => {
      this.inputText = await Promise.resolve(this.displayWith(this.value)) ?? "";
    });
  }
  async handleValueChange(oldValue, value) {
    if (value !== oldValue) {
      await this.syncDisplayValue();
      const options = this.getAllOptions();
      this.setSelectedOption(null);
      for (const option of options) {
        if (option.value === this.value) {
          this.setSelectedOption(option);
          this.setCurrentOption(option);
          break;
        }
      }
      this.updateComplete.then(() => {
        this.pendingChangeEvent && this.emit("flow-ui-change");
        this.pendingInputEvent && this.emit("flow-ui-input");
        this.pendingChangeEvent = false;
        this.pendingInputEvent = false;
      });
    }
  }
  /** Focuses on the control */
  focus() {
    if (!this.hasFocus) {
      this.hasFocus = true;
      this.input.focus({ preventScroll: true });
      this.emit("flow-ui-focus");
      if (this.clearOnFocus) {
        if (isStringFull(this.value)) {
          this.setValue("", { emitInputEvent: true, emitChangeEvent: true });
        } else {
          this.inputText = "";
        }
        this.hasOptions = false;
      }
      this.toggleListbox();
      this.addListenersForDropdown();
    }
  }
  /** Blurs from the control. */
  blur() {
    if (this.hasFocus) {
      this.input.blur();
      this.hasFocus = false;
      this.toggleListbox();
      this.removeListenersForDropdown();
      this.emit("flow-ui-blur");
    }
  }
  /** Shows the listbox. */
  async show() {
    if (!this.open) {
      this.emit("flow-ui-show");
      await stopAnimations(this);
      this.open = true;
      const { keyframes, options } = getAnimation(this, "autocomplete.show", { dir: this.localize.dir() });
      await animateTo(this.popup.popup, keyframes, options);
      this.emit("flow-ui-after-show");
    }
  }
  /** Hides the listbox. */
  async hide() {
    if (this.open) {
      this.emit("flow-ui-hide");
      await stopAnimations(this);
      const { keyframes, options } = getAnimation(this, "autocomplete.hide", { dir: this.localize.dir() });
      await animateTo(this.popup.popup, keyframes, options);
      this.open = false;
      this.emit("flow-ui-after-hide");
    }
  }
  /** Gets the current value of the text input */
  getInputText() {
    return this.inputText;
  }
  /** @internal */
  render() {
    const hasLabelSlot = this.hasSlotController.test("label");
    const hasHelpTextSlot = this.hasSlotController.test("help-text");
    const hasLabel = !!this.label || !!hasLabelSlot;
    const hasHelpText = !!this.helpText || !!hasHelpTextSlot;
    const hasClearIcon = this.clearable && isStringFull(this.value) && !this.disabled;
    const isPlaceholderVisible = this.placeholder && isStringFull(this.inputText) === false;
    const horizontalPlacement = this.inline ? "end" : "start";
    const placement = `${this.placement}-${horizontalPlacement}`;
    const strategy = this.inline || this.hoist ? "fixed" : "absolute";
    const sync = this.inline ? void 0 : "width";
    return html`
      <div
        part="form-control"
        class=${classMap({
      "form-control": true,
      "form-control--small": this.size === "small",
      "form-control--medium": this.size === "medium",
      "form-control--large": this.size === "large",
      "form-control--inline": this.inline,
      "form-control--disabled": this.disabled,
      "form-control--focused": this.hasFocus,
      "form-control--has-label": hasLabel,
      "form-control--has-help-text": hasHelpText,
      "form-control--has-divider": this.divider,
      "form-control--filled": this.filled,
      "form-control--pill": this.pill
    })}
      >
        <label id="label" part="label" class="form-control__label" aria-hidden=${hasLabel ? "false" : "true"}>
          <slot name="label">${this.label}</slot>
        </label>

        <div
          part="help-text"
          id="help-text"
          class="form-control__help-text"
          aria-hidden=${hasHelpText ? "false" : "true"}
        >
          <slot name="help-text">${this.helpText}</slot>
        </div>

        <div part="control" class="form-control__input">
          <flow-ui-popup
            class=${classMap({
      autocomplete: true,
      "autocomplete--standard": true,
      "autocomplete--pill": this.pill,
      "autocomplete--open": this.open,
      "autocomplete--disabled": this.disabled,
      "autocomplete--inline": this.inline,
      "autocomplete--focused": this.hasFocus,
      "autocomplete--placeholder-visible": isPlaceholderVisible,
      "autocomplete--top": this.placement === "top",
      "autocomplete--bottom": this.placement === "bottom",
      "autocomplete--small": this.size === "small",
      "autocomplete--medium": this.size === "medium",
      "autocomplete--large": this.size === "large"
    })}
            ?active=${this.open}
            placement=${ifDefined(placement)}
            strategy=${ifDefined(strategy)}
            sync=${ifDefined(sync)}
            flip
            shift
            auto-size="vertical"
            auto-size-padding="10"
            @keydown=${this.handleKeyDown}
          >
            <div
              part="combobox"
              class="autocomplete__combobox"
              slot="anchor"
              @keydown=${this.handleDocumentEvent}
              @mousedown=${this.handleDocumentEvent}
            >
              <slot part="prefix" name="prefix" class="autocomplete__prefix"></slot>

              <div class="autocomplete__input-wrapper">
                <input
                  part="display-input"
                  class="autocomplete__input"
                  type="text"
                  autocomplete="off"
                  spellcheck="false"
                  autocapitalize="off"
                  aria-controls="listbox"
                  aria-expanded=${this.open ? "true" : "false"}
                  aria-haspopup="listbox"
                  aria-labelledby="label"
                  aria-disabled=${this.disabled ? "true" : "false"}
                  aria-describedby="help-text"
                  role="combobox"
                  tabindex="0"
                  placeholder=${this.placeholder}
                  .value=${this.inputText}
                  ?disabled=${this.disabled}
                  @focus=${this.handleInputFocus}
                  @blur=${this.handleInputBlur}
                  @input=${this.handleInput}
                  @change=${this.handleInputChange}
                />
                <span class="autocomplete__input-sizer">${this.inputText || this.placeholder}</span>
              </div>

              ${hasClearIcon ? html`
                    <button
                      part="clear-button"
                      class="autocomplete__clear"
                      type="button"
                      aria-label=${this.localize.term("clearEntry")}
                      @mousedown=${this.handleClearMouseDown}
                      @click=${this.handleClearClick}
                      tabindex="-1"
                    >
                      <slot name="clear-icon">
                        <flow-ui-icon name="x-circle-fill" library="system"></flow-ui-icon>
                      </slot>
                    </button>
                  ` : nothing}

              <slot part="suffix" name="suffix" class="autocomplete__suffix"></slot>
            </div>

            <div
              id="listbox"
              role="listbox"
              part="listbox"
              class="autocomplete__listbox"
              aria-labelledby="label"
              aria-expanded=${this.open ? "true" : "false"}
              aria-multiselectable="false"
              @mouseup=${this.handleListboxClick}
              @slotchange=${this.handleDefaultSlotChange}
            >
              <slot></slot>
            </div>
          </flow-ui-popup>
        </div>

        ${this.divider ? this.renderDivider() : nothing}
      </div>
    `;
  }
}
FlowUiAutocomplete.styles = styles;
FlowUiAutocomplete.dependencies = {
  "flow-ui-popup": FlowUiPopup,
  "flow-ui-icon": FlowUiIcon
};
__decorateClass([
  property({ reflect: true })
], FlowUiAutocomplete.prototype, "size", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiAutocomplete.prototype, "filled", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiAutocomplete.prototype, "pill", 2);
__decorateClass([
  property()
], FlowUiAutocomplete.prototype, "label", 2);
__decorateClass([
  property({ attribute: "help-text" })
], FlowUiAutocomplete.prototype, "helpText", 2);
__decorateClass([
  property()
], FlowUiAutocomplete.prototype, "placeholder", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiAutocomplete.prototype, "disabled", 2);
__decorateClass([
  property()
], FlowUiAutocomplete.prototype, "form", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiAutocomplete.prototype, "required", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiAutocomplete.prototype, "inline", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiAutocomplete.prototype, "divider", 2);
__decorateClass([
  property({ type: Boolean })
], FlowUiAutocomplete.prototype, "hoist", 2);
__decorateClass([
  property({ reflect: true })
], FlowUiAutocomplete.prototype, "placement", 2);
__decorateClass([
  property()
], FlowUiAutocomplete.prototype, "value", 2);
__decorateClass([
  property()
], FlowUiAutocomplete.prototype, "displayWith", 2);
__decorateClass([
  property({ reflect: true, attribute: "debounce-delay", type: Number })
], FlowUiAutocomplete.prototype, "debounceDelay", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiAutocomplete.prototype, "clearable", 2);
__decorateClass([
  property({ type: Boolean, reflect: true, attribute: "clear-on-focus" })
], FlowUiAutocomplete.prototype, "clearOnFocus", 2);
__decorateClass([
  state()
], FlowUiAutocomplete.prototype, "inputText", 2);
__decorateClass([
  state()
], FlowUiAutocomplete.prototype, "open", 2);
__decorateClass([
  state()
], FlowUiAutocomplete.prototype, "selectedOption", 2);
__decorateClass([
  state()
], FlowUiAutocomplete.prototype, "currentOption", 2);
__decorateClass([
  state()
], FlowUiAutocomplete.prototype, "hasFocus", 2);
__decorateClass([
  query(".autocomplete__input")
], FlowUiAutocomplete.prototype, "input", 2);
__decorateClass([
  query(".autocomplete")
], FlowUiAutocomplete.prototype, "popup", 2);
__decorateClass([
  watch("debounceDelay")
], FlowUiAutocomplete.prototype, "updateDebounceDelay", 1);
__decorateClass([
  watch("value")
], FlowUiAutocomplete.prototype, "handleValueChange", 1);
setDefaultAnimation("autocomplete.show", {
  keyframes: [
    { opacity: 0, scale: 0.9 },
    { opacity: 1, scale: 1 }
  ],
  options: { duration: 100, easing: "ease" }
});
setDefaultAnimation("autocomplete.hide", {
  keyframes: [
    { opacity: 1, scale: 1 },
    { opacity: 0, scale: 0.9 }
  ],
  options: { duration: 100, easing: "ease" }
});

export { FlowUiAutocomplete as default };
