import { formatLocale } from 'd3-format';
import { html, nothing } from 'lit';
import { state, query, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { live } from 'lit/directives/live.js';
import { boolean } from '../../internal/boolean.mjs';
import { defaultValue } from '../../internal/default-value.mjs';
import { FlowRdfFormControl } from '../../internal/flow-ui-rdf-element.mjs';
import { FormControlController } from '../../internal/form.mjs';
import { HasSlotController } from '../../internal/slot.mjs';
import { isStringFull, isNil, isNumber, isNumeric, isNumberLike } from '../../internal/type-checks.mjs';
import { watch } from '../../internal/watch.mjs';
import { LocalizeController } from '../../utilities/localize.mjs';
import styles from './input.styles.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;
};
const d3Format = formatLocale({
  minus: "-",
  decimal: ".",
  thousands: ",",
  grouping: [3],
  currency: ["\xA3", ""]
}).format;
const DEFAULT_FORMATTER = (val) => isNil(val) ? "--" : val.toString();
class FlowUiInput extends FlowRdfFormControl {
  constructor() {
    super(...arguments);
    this.formControlController = new FormControlController(this, {
      assumeInteractionOn: ["flow-ui-blur", "flow-ui-input"]
    });
    this.hasSlotController = new HasSlotController(this, "help-text", "label");
    this.localize = new LocalizeController(this);
    this._numberFormatter = DEFAULT_FORMATTER;
    this.hasFocus = false;
    this.title = "";
    this.type = "text";
    this.name = "";
    this.value = "";
    this.defaultValue = "";
    this.size = "medium";
    this.filled = false;
    this.pill = false;
    this.label = "";
    this.helpText = "";
    this.clearable = false;
    this.disabled = false;
    this.placeholder = "";
    this.format = "";
    this.readonly = false;
    this.passwordToggle = false;
    this.passwordVisible = false;
    this.noStepButtons = false;
    this.form = "";
    this.required = false;
    this.spellcheck = true;
    this.inline = false;
    this.divider = false;
  }
  connectedCallback() {
    super.connectedCallback();
    this.disabled = boolean(this.disabled, true);
    this.required = boolean(this.required, true);
    this.readonly = boolean(this.readonly, true);
    this.autofocus = boolean(this.autofocus, true);
    this.noStepButtons = boolean(this.noStepButtons, true);
    this.passwordToggle = boolean(this.passwordToggle, true);
    this.passwordVisible = boolean(this.passwordVisible, true);
    this.inline = boolean(this.inline, true);
    this.divider = boolean(this.divider, true);
  }
  handleFormatChange() {
    if (isStringFull(this.format)) {
      try {
        const fn = d3Format(this.format);
        this._numberFormatter = (val) => {
          if (isNil(val)) {
            return "";
          } else if (isNumber(val) || isNumeric(val)) {
            return fn(+val);
          } else {
            return val;
          }
        };
      } catch (e) {
        console.warn(`Invalid d3 format string: ${this.format}`);
        this._numberFormatter = DEFAULT_FORMATTER;
      }
    } else {
      this._numberFormatter = DEFAULT_FORMATTER;
    }
  }
  //
  // NOTE: We use an in-memory input for these getters/setters instead of the one in the template because the properties
  // can be set before the component is rendered.
  //
  /** Gets or sets the current value as a `Date` object. Returns `null` if the value can't be converted. */
  get valueAsDate() {
    const input = document.createElement("input");
    input.type = "date";
    input.value = this.value;
    return input.valueAsDate;
  }
  set valueAsDate(newValue) {
    const input = document.createElement("input");
    input.type = "date";
    input.valueAsDate = newValue;
    this.value = input.value;
  }
  /** Gets or sets the current value as a number. Returns `NaN` if the value can't be converted. */
  get valueAsNumber() {
    const input = document.createElement("input");
    input.type = "number";
    input.value = this.value;
    return input.valueAsNumber;
  }
  set valueAsNumber(newValue) {
    const input = document.createElement("input");
    input.type = "number";
    input.valueAsNumber = newValue;
    this.value = input.value;
  }
  /** Gets the validity state object */
  get validity() {
    return this.input.validity;
  }
  /** Gets the validation message */
  get validationMessage() {
    return this.input.validationMessage;
  }
  firstUpdated() {
    this.formControlController.updateValidity();
  }
  handleBlur() {
    this.hasFocus = false;
    this.emit("flow-ui-blur");
  }
  handleChange() {
    if (isStringFull(this.format)) {
      this.input.value = this.toDisplayValue(this.clampValue(this.input.value).toString());
    }
    this.value = this.fromDisplayValue(this.input.value);
    this.emit("flow-ui-change");
  }
  handleClearClick(event) {
    this.value = "";
    this.emit("flow-ui-clear");
    this.emit("flow-ui-input");
    this.emit("flow-ui-change");
    this.input.focus();
    event.stopPropagation();
  }
  handleFocus() {
    this.hasFocus = true;
    this.emit("flow-ui-focus");
  }
  handleInput(ev) {
    ev.stopPropagation();
    if (this.type !== "number" || !isStringFull(this.format)) {
      this.value = this.input.value;
      this.formControlController.updateValidity();
      this.emit("flow-ui-input");
    }
  }
  handleInvalid(event) {
    this.formControlController.setValidity(false);
    this.formControlController.emitInvalidEvent(event);
  }
  handleKeyDown(event) {
    const hasModifier = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
    if (event.key === "Enter" && !hasModifier) {
      setTimeout(() => {
        if (!event.defaultPrevented && !event.isComposing) {
          this.formControlController.submit();
        }
      });
    }
  }
  handlePasswordToggle() {
    this.passwordVisible = !this.passwordVisible;
  }
  handleDisabledChange() {
    this.formControlController.setValidity(this.disabled);
  }
  handleStepChange() {
    this.input.step = String(this.step);
    this.formControlController.updateValidity();
  }
  async handleValueChange() {
    await this.updateComplete;
    this.formControlController.updateValidity();
  }
  /** Sets focus on the input. */
  focus(options) {
    this.input.focus(options);
  }
  /** Removes focus from the input. */
  blur() {
    this.input.blur();
  }
  /** Selects all the text in the input. */
  select() {
    this.input.select();
  }
  /** Sets the start and end positions of the text selection (0-based). */
  setSelectionRange(selectionStart, selectionEnd, selectionDirection = "none") {
    this.input.setSelectionRange(selectionStart, selectionEnd, selectionDirection);
  }
  /** Replaces a range of text with a new string. */
  setRangeText(replacement, start, end, selectMode) {
    this.input.setRangeText(replacement, start, end, selectMode);
    if (this.value !== this.input.value) {
      this.value = this.fromDisplayValue(this.input.value);
    }
  }
  /** Displays the browser picker for an input element (only works if the browser supports it for the input type). */
  showPicker() {
    if ("showPicker" in HTMLInputElement.prototype) {
      this.input.showPicker();
    }
  }
  /** Increments the value of a numeric input type by the value of the step attribute. */
  stepUp() {
    this.input.stepUp();
    if (this.type === "number") {
      this.input.value = this.toDisplayValue(this.input.value);
    }
    if (this.value !== this.input.value) {
      this.value = this.fromDisplayValue(this.input.value);
    }
  }
  /** Decrements the value of a numeric input type by the value of the step attribute. */
  stepDown() {
    this.input.stepDown();
    if (this.type === "number") {
      this.input.value = this.toDisplayValue(this.input.value);
    }
    if (this.value !== this.input.value) {
      this.value = this.fromDisplayValue(this.input.value);
    }
  }
  /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */
  checkValidity() {
    return this.input.checkValidity();
  }
  /** Gets the associated form, if one exists. */
  getForm() {
    return this.formControlController.getForm();
  }
  /** 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(message) {
    this.input.setCustomValidity(message);
    this.formControlController.updateValidity();
  }
  fromDisplayValue(displayValue) {
    if (this.type === "number") {
      return parseFloat(displayValue).toString();
    }
    return displayValue;
  }
  toDisplayValue(value) {
    if (this.type === "number") {
      return this._numberFormatter(value);
    }
    return value;
  }
  /** Value to use for input sizer in inline mode */
  toSizeValue(value) {
    if (this.type === "number") {
      return this._numberFormatter(value).replace(/\d/g, "8");
    }
    return value;
  }
  clampValue(value) {
    if (this.type === "number") {
      const min = isNumberLike(this.min) ? +this.min : Number.NEGATIVE_INFINITY;
      const max = isNumberLike(this.max) ? +this.max : Number.POSITIVE_INFINITY;
      return Math.min(Math.max(+value, min), max).toString();
    }
    return value;
  }
  render() {
    const hasLabelSlot = this.hasSlotController.test("label");
    const hasHelpTextSlot = this.hasSlotController.test("help-text");
    const hasLabel = this.label ? true : !!hasLabelSlot;
    const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
    const hasClearIcon = this.clearable && !this.disabled && !this.readonly && (isNumber(this.value) || isStringFull(this.value));
    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
          part="form-control-label"
          class="form-control__label"
          for="input"
          aria-hidden=${hasLabel ? "false" : "true"}
        >
          <slot name="label">${this.label}</slot>
        </label>

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

        <div part="form-control-input" class=${classMap({
      "form-control__input": true,
      "input--empty": !this.value,
      // Always hide the native spin buttons
      "input--no-spin-buttons": true
    })}>

            <slot name="prefix" part="prefix" class="input__prefix"></slot>
            <div class="input__wrapper">
            <input
              part="input"
              id="input"
              class="input__control"
              type=${this.type === "password" && this.passwordVisible ? "text" : this.type}
              title=${""}
              name=${ifDefined(this.name)}
              ?disabled=${this.disabled}
              ?readonly=${this.readonly}
              ?required=${this.required}
              placeholder=${ifDefined(this.placeholder)}
              minlength=${ifDefined(this.minlength)}
              maxlength=${ifDefined(this.maxlength)}
              min=${ifDefined(this.min)}
              max=${ifDefined(this.max)}
              step=${ifDefined(this.step)}
              .value=${live(this.toDisplayValue(this.value))}
              autocapitalize=${ifDefined(this.autocapitalize)}
              autocomplete=${ifDefined(this.autocomplete)}
              autocorrect=${ifDefined(this.autocorrect)}
              ?autofocus=${this.autofocus}
              spellcheck=${this.spellcheck}
              pattern=${ifDefined(this.pattern)}
              enterkeyhint=${ifDefined(this.enterkeyhint)}
              inputmode=${ifDefined(this.inputmode)}
              aria-describedby="help-text"
              @change=${this.handleChange}
              @input=${this.handleInput}
              @invalid=${this.handleInvalid}
              @keydown=${this.handleKeyDown}
              @focus=${this.handleFocus}
              @blur=${this.handleBlur}
            />
            ${// Ensure that prettier does not mess up the formatting of the following html. There should be no extraneous whitespace
    // in the sizer span.
    // prettier-ignore
    this.inline ? html`<span part="input-sizer" class="input__sizer">${this.toSizeValue(this.value) || this.placeholder}</span>` : nothing}
            </div>

            ${hasClearIcon ? html`
                    <button
                      part="clear-button"
                      class="input__clear"
                      type="button"
                      aria-label=${this.localize.term("clearEntry")}
                      @click=${this.handleClearClick}
                      tabindex="-1"
                    >
                      <slot name="clear-icon">
                        <flow-ui-icon name="x-circle-fill" library="system"></flow-ui-icon>
                      </slot>
                    </button>
                  ` : ""}
            ${this.passwordToggle && !this.disabled ? html`
                    <button
                      part="password-toggle-button"
                      class="input__password-toggle"
                      type="button"
                      aria-label=${this.localize.term(this.passwordVisible ? "hidePassword" : "showPassword")}
                      @click=${this.handlePasswordToggle}
                      tabindex="-1"
                    >
                      ${this.passwordVisible ? html`
                            <slot name="show-password-icon">
                              <flow-ui-icon name="eye-slash" library="system"></flow-ui-icon>
                            </slot>
                          ` : html`
                            <slot name="hide-password-icon">
                              <flow-ui-icon name="eye" library="system"></flow-ui-icon>
                            </slot>
                          `}
                    </button>
                  ` : ""}


            <slot name="suffix" part="suffix" class="input__suffix"></slot>
            ${this.type === "number" && !this.noStepButtons ? html`
                    <div slot="suffix" class="input__stepper-button-container">
                      <flow-ui-icon-button
                        class="input__stepper-button"
                        library="system"
                        name="chevron-up"
                        tabindex="-1"
                        ?disabled=${this.disabled}
                        @click=${() => {
      this.stepUp();
      this.handleChange();
    }}
                      ></flow-ui-icon-button>
                      <flow-ui-icon-button
                        class="input__stepper-button"
                        library="system"
                        name="chevron-down"
                        tabindex="-1"
                        ?disabled=${this.disabled}
                        @click=${() => {
      this.stepDown();
      this.handleChange();
    }}
                      ></flow-ui-icon-button>
                    </div>
                  ` : nothing}

        </div>
        ${this.divider ? this.renderDivider() : nothing}
        </div>
      </div>
    `;
  }
}
FlowUiInput.styles = styles;
__decorateClass([
  state()
], FlowUiInput.prototype, "_numberFormatter", 2);
__decorateClass([
  query(".input__control")
], FlowUiInput.prototype, "input", 2);
__decorateClass([
  state()
], FlowUiInput.prototype, "hasFocus", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "title", 2);
__decorateClass([
  property({ reflect: true })
], FlowUiInput.prototype, "type", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "name", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "value", 2);
__decorateClass([
  defaultValue()
], FlowUiInput.prototype, "defaultValue", 2);
__decorateClass([
  property({ reflect: true })
], FlowUiInput.prototype, "size", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiInput.prototype, "filled", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiInput.prototype, "pill", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "label", 2);
__decorateClass([
  property({ attribute: "help-text" })
], FlowUiInput.prototype, "helpText", 2);
__decorateClass([
  property({ type: Boolean })
], FlowUiInput.prototype, "clearable", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiInput.prototype, "disabled", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "placeholder", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "format", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiInput.prototype, "readonly", 2);
__decorateClass([
  property({ attribute: "password-toggle", type: Boolean })
], FlowUiInput.prototype, "passwordToggle", 2);
__decorateClass([
  property({ attribute: "password-visible", type: Boolean })
], FlowUiInput.prototype, "passwordVisible", 2);
__decorateClass([
  property({ attribute: "no-step-buttons", type: Boolean })
], FlowUiInput.prototype, "noStepButtons", 2);
__decorateClass([
  property({ reflect: true })
], FlowUiInput.prototype, "form", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiInput.prototype, "required", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "pattern", 2);
__decorateClass([
  property({ type: Number })
], FlowUiInput.prototype, "minlength", 2);
__decorateClass([
  property({ type: Number })
], FlowUiInput.prototype, "maxlength", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "min", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "max", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "step", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "autocapitalize", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "autocorrect", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "autocomplete", 2);
__decorateClass([
  property({ type: Boolean })
], FlowUiInput.prototype, "autofocus", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "enterkeyhint", 2);
__decorateClass([
  property({
    type: Boolean,
    converter: {
      // Allow "true|false" attribute values but keep the property boolean
      fromAttribute: (value) => boolean(value),
      toAttribute: (value) => value ? "true" : "false"
    }
  })
], FlowUiInput.prototype, "spellcheck", 2);
__decorateClass([
  property()
], FlowUiInput.prototype, "inputmode", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiInput.prototype, "inline", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiInput.prototype, "divider", 2);
__decorateClass([
  watch("format")
], FlowUiInput.prototype, "handleFormatChange", 1);
__decorateClass([
  watch("disabled", { waitUntilFirstUpdate: true })
], FlowUiInput.prototype, "handleDisabledChange", 1);
__decorateClass([
  watch("step", { waitUntilFirstUpdate: true })
], FlowUiInput.prototype, "handleStepChange", 1);
__decorateClass([
  watch("value", { waitUntilFirstUpdate: true })
], FlowUiInput.prototype, "handleValueChange", 1);

export { FlowUiInput as default };
