import { Controller } from "stimulus";
import TomSelect from "tom-select/dist/esm/tom-select.complete";
import showHttpError from "../../webpacker/js/services/show_http_error";

export default class extends Controller {
  static values = {
    options: Object,
    placeholder: String,
    remoteUrl: String,
  };

  connect(options = {}) {
    let tomSelectOptions = {
      maxItems: 1,
      maxOptions: null,
      plugins: ["dropdown_input"],
      allowEmptyOption: true, // Show blank option (option with empty value)
      placeholder: this.placeholderValue || this.#emptyOption(),
      onItemAdd: function () {
        this.setTextboxValue("");
      },
      ...this.optionsValue,
      ...options,
    };

    if (this.remoteUrlValue) {
      this.#addRemoteOptions(tomSelectOptions);
    }

    this.control = new TomSelect(this.element, tomSelectOptions);
  }

  disconnect() {
    if (this.control) this.control.destroy();
  }

  // private

  #emptyOption() {
    const optionsArray = [...this.element.options];
    return optionsArray.find((option) => [null, ""].includes(option.value))?.text;
  }

  #buildUrl(query, page = 1) {
    const url = new URL(this.remoteUrlValue, window.location.origin);
    url.searchParams.set("q", query);
    url.searchParams.set("page", page);
    return url.toString();
  }

  #fetchOptions(query, callback) {
    const url = this.control.getUrl(query);

    fetch(url)
      .then((response) => {
        if (!response.ok) {
          showHttpError(response.status);
          throw response;
        }
        return response.json();
      })
      .then((json) => {
        /**
         * Expected API shape:
         * {
         *   results: [{ value, label }],
         *   pagination: { more: boolean }
         * }
         */
        if (json.pagination?.more) {
          const currentUrl = new URL(url);
          const currentPage = parseInt(currentUrl.searchParams.get("page") || "1");
          const nextUrl = this.#buildUrl(query, currentPage + 1);
          this.control.setNextUrl(query, nextUrl);
        }

        callback(json.results || []);
      })
      .catch((error) => {
        callback();
        console.error(error);
      });
  }

  #addRemoteOptions(options) {
    this.openedByClick = false;

    options.firstUrl = (query) => {
      return this.#buildUrl(query, 1);
    };

    options.load = this.#fetchOptions.bind(this);

    options.onFocus = function () {
      this.control.load("", () => {});
    }.bind(this);

    options.onDropdownOpen = function () {
      this.openedByClick = true;
    }.bind(this);

    options.onType = function () {
      this.openedByClick = false;
    }.bind(this);

    // As per TomSelect source code, Loading state is shown on the UI when this function returns true.
    // By default it shows loading state only when there is some input in the search box.
    // We want to show loading state on focus as well (when there is no input) to indicate that options are being loaded.
    options.shouldLoad = function (query) {
      return this.openedByClick || query.length > 0;
    }.bind(this);

    options.valueField = "value";
    options.labelField = "label";
    options.searchField = "label";
  }
}
