export interface AutocompleteConfig {
  autoselect: boolean;
  autocomplete?: string;
  wrapperClassName: string;
  menuClassName: string;
  optionClassName: string;
  customInputClassName: string;
  displayMenu: string;
  onConfirm?: (value: unknown) => void;
  tClearButton: () => string;
  tAssistiveHint: () => string;
}

export const defaultConfig: AutocompleteConfig = {
  autoselect: true,
  wrapperClassName: "",
  menuClassName: "",
  optionClassName: "",
  customInputClassName: "",
  displayMenu: "overlay",
  tClearButton: () => "Vymazať text",
  tAssistiveHint: () =>
    "Pre prezeranie výsledkov automatického dopĺňania použite šípky nahor a nadol a pre výber konkrétneho výsledku použite tlačidlo Enter. Pri dotykových zariadeniach použite dotyk alebo potiahnutie prstom do strany.",
};

const addClassToken = (className: string, token: string) => {
  const classTokens = className.split(/\s+/).filter(Boolean);

  if (classTokens.includes(token)) {
    return className;
  }

  return [...classTokens, token].join(" ");
};

export default class Autocomplete {
  private element: HTMLElement;
  private config: AutocompleteConfig;
  private instance: any;
  private teardownClearButton: (() => void) | null;

  constructor(element: HTMLElement, config?: Partial<AutocompleteConfig>) {
    this.element = element;
    this.config = { ...defaultConfig, ...config };
    this.onPageshow = this.onPageshow.bind(this);
    this.onClearButtonClick = this.onClearButtonClick.bind(this);
    this.instance = null;
    this.teardownClearButton = null;

    (this.element as any).ODS_Autocomplete = this;

    // Initialize asynchronously to handle dynamic imports
    this.init().catch(() => undefined);

    return this;
  }

  private async init(): Promise<void> {
    const configAttr = this.element.getAttribute("data-autocomplete-config");
    let elementConfig: Partial<AutocompleteConfig> = {};
    if (configAttr) {
      try {
        elementConfig = JSON.parse(configAttr);
      } catch {
        // ignore parse error
      }
    }
    this.config = { ...this.config, ...elementConfig };

    const customInputClassName = this.config.customInputClassName || "";
    const customInputClassTokens = customInputClassName
      .split(/\s+/)
      .filter(Boolean);
    const wrapperClassTokens = this.config.wrapperClassName
      .split(/\s+/)
      .filter(Boolean);

    const hasSearchIcon =
      customInputClassTokens.includes("input--search-icon") ||
      wrapperClassTokens.includes("autocomplete__wrapper--search-icon");
    const hasSearchIconWithPlaceholder =
      customInputClassTokens.includes("input--search-icon-with-placeholder") ||
      wrapperClassTokens.includes(
        "autocomplete__wrapper--search-icon-with-placeholder",
      );
    const isInvalid = this.element.classList.contains("is-invalid");

    this.config.customInputClassName = customInputClassTokens
      .filter(
        (token) =>
          token !== "input--search-icon" &&
          token !== "input--search-icon-with-placeholder",
      )
      .join(" ");

    if (isInvalid) {
      this.config.customInputClassName = addClassToken(
        this.config.customInputClassName,
        "is-invalid",
      );
    }

    if (hasSearchIcon) {
      this.config.wrapperClassName = addClassToken(
        this.config.wrapperClassName,
        "autocomplete__wrapper--search-icon",
      );
    }

    if (hasSearchIconWithPlaceholder) {
      this.config.wrapperClassName = addClassToken(
        this.config.wrapperClassName,
        "autocomplete__wrapper--search-icon-with-placeholder",
      );
    }

    // Dynamic import to avoid SSR issues
    const accessibleAutocomplete = (
      await import("@orangesk/accessible-autocomplete")
    ).default;

    if (this.element.tagName === "SELECT") {
      accessibleAutocomplete.enhanceSelectElement({
        selectElement: this.element,
        ...this.config,
      });
    } else {
      accessibleAutocomplete({
        element: this.element,
        ...this.config,
      });
    }

    this.initEnhancedControls(hasSearchIconWithPlaceholder);

    window.addEventListener("pageshow", this.onPageshow);
  }

  private getWrapper(): HTMLElement | null {
    if (this.element.tagName === "SELECT") {
      const rootElement = this.element.previousElementSibling;
      if (!(rootElement instanceof HTMLElement)) {
        return null;
      }

      if (rootElement.classList.contains("autocomplete__wrapper")) {
        return rootElement;
      }

      return rootElement.querySelector(".autocomplete__wrapper");
    }

    return this.element.querySelector(".autocomplete__wrapper");
  }

  private getInput(): HTMLInputElement | null {
    return this.getWrapper()?.querySelector(".autocomplete__input") ?? null;
  }

  private initEnhancedControls(
    hasSearchIconWithPlaceholder: boolean,
    retries = 10,
  ): void {
    if (!this.getInput()) {
      if (retries > 0) {
        window.setTimeout(
          () =>
            this.initEnhancedControls(
              hasSearchIconWithPlaceholder,
              retries - 1,
            ),
          0,
        );
      }

      return;
    }

    this.syncInputState();

    if (hasSearchIconWithPlaceholder) {
      this.initClearButton();
    }
  }

  private syncInputState(): void {
    const input = this.getInput();
    if (!input) {
      return;
    }

    if (this.element.classList.contains("is-invalid")) {
      input.classList.add("is-invalid");
      input.setAttribute("aria-invalid", "true");
    }

    if (this.element instanceof HTMLSelectElement && this.element.disabled) {
      input.disabled = true;
    }
  }

  private initClearButton(): void {
    this.teardownClearButton?.();

    const wrapper = this.getWrapper();
    const input = this.getInput();

    if (!wrapper || !input) {
      return;
    }

    const clearButton = document.createElement("button");
    clearButton.type = "button";
    clearButton.className = "autocomplete__clear";
    clearButton.setAttribute("aria-label", this.config.tClearButton());

    const updateClearButton = () => {
      clearButton.hidden = input.disabled || input.value.length === 0;
    };
    const scheduleUpdateClearButton = () => {
      window.setTimeout(updateClearButton, 0);
    };

    clearButton.addEventListener("mousedown", this.preventDefault);
    clearButton.addEventListener("keydown", this.stopPropagation);
    clearButton.addEventListener("click", this.onClearButtonClick);
    input.addEventListener("input", updateClearButton);
    input.addEventListener("change", updateClearButton);
    wrapper.addEventListener("click", scheduleUpdateClearButton);
    wrapper.addEventListener("keyup", scheduleUpdateClearButton);

    updateClearButton();
    wrapper.appendChild(clearButton);

    this.teardownClearButton = () => {
      clearButton.removeEventListener("mousedown", this.preventDefault);
      clearButton.removeEventListener("keydown", this.stopPropagation);
      clearButton.removeEventListener("click", this.onClearButtonClick);
      input.removeEventListener("input", updateClearButton);
      input.removeEventListener("change", updateClearButton);
      wrapper.removeEventListener("click", scheduleUpdateClearButton);
      wrapper.removeEventListener("keyup", scheduleUpdateClearButton);
      clearButton.remove();
    };
  }

  private preventDefault(event: Event): void {
    event.preventDefault();
  }

  private stopPropagation(event: Event): void {
    event.stopPropagation();
  }

  private onClearButtonClick(): void {
    const input = this.getInput();

    if (!input) {
      return;
    }

    input.value = "";
    input.dispatchEvent(new Event("input", { bubbles: true }));
    input.dispatchEvent(new Event("change", { bubbles: true }));

    if (this.element instanceof HTMLSelectElement) {
      const emptyOption = Array.from(this.element.options).find(
        (option) => option.value === "",
      );

      if (emptyOption) {
        emptyOption.selected = true;
      } else {
        this.element.selectedIndex = -1;
      }

      this.element.dispatchEvent(new Event("change", { bubbles: true }));
    }

    this.config.onConfirm?.("");
    input.focus();
  }

  private onPageshow(e: PageTransitionEvent): void {
    if (e.persisted && window) {
      window.location.reload();
    }
  }

  public destroy(): void {
    this.teardownClearButton?.();
    this.teardownClearButton = null;
    window.removeEventListener("pageshow", this.onPageshow);
    (this.element as any).ODS_Autocomplete = null;
  }

  public update(): void {
    this.destroy();
    this.init().catch(() => undefined);
  }

  static getInstance(el: HTMLElement): Autocomplete | null {
    return el && (el as any).ODS_Autocomplete
      ? (el as any).ODS_Autocomplete
      : null;
  }
}
