import { InobyModule } from "./inoby-module";
import { getCookie } from "typescript-cookie";

/**
 * Use WP Rest api for fetching and filtering of post type.
 * Requires element marked with "post-container" attribute to insert fetched post cards
 */
class PostFilteringBase extends InobyModule {
  private post_container: HTMLElement;
  private post_type: string;
  private total_pages = 1;
  private total_count: string = "";
  private timeout: NodeJS.Timeout = null;

  protected page = 1;
  protected page_size: number = null;
  protected search_str = "";
  protected order_by: string;
  protected order_by_default: string;
  protected ascdesc: "asc" | "desc" = "asc";
  protected ascdesc_default: "asc" | "desc" = "asc";
  protected tax: any = {};

  run() {}

  /**
   * fetches next page of post cards
   */
  paginate() {
    if (this.page < this.total_pages) {
      this.page++;
      this.fetch(false);
    }
  }

  /**
   * makes search through the posts
   * pagination is rest to first page
   * @param s search string
   */
  search(s: string, debounce = false) {
    this.search_str = s;
    if (debounce) {
      this.fetchTimeout();
    } else {
      this.fetch();
    }
  }

  /**
   * orders post cards by provided field
   * pagination is rest to first page
   * @param field order field name (date, title, changed, author, ...)
   * @param ascdesc asc/desc order direction
   */
  orderBy(field: string = null, ascdesc: "asc" | "desc" = null) {
    if (ascdesc !== null) {
      this.ascdesc = ascdesc == "asc" ? "asc" : "desc";
    }
    if (field !== null) {
      this.order_by = field;
    }
    this.fetch();
  }

  /**
   * filters the cards by taxonomy
   * @param tax taxonomy slug
   * @param value taxonomy value string for one | string [] for many | null for reset
   */
  taxonomy(tax: string, value: string[] | string | null) {
    this.tax[tax] = value;
    this.fetch();
  }

  /**
   * fetches the cards with 100ms timeout
   * can be handy while typing into search input
   * @param clear false if fetched cards should be appended to current cards
   */
  fetchTimeout(clear: boolean = true) {
    clearTimeout(this.timeout);
    this.timeout = setTimeout(() => this.fetch(clear), 100);
  }

  /**
   * fetches the cards based on current filter and order data
   * @param clear false if fetched cards should be appended to current cards
   * @returns JQuery.jqXHR<any>
   */
  fetch(clear: boolean = true) {
    clearTimeout(this.timeout);
    if (clear) {
      this.page = 1;
    }

    const tax: any = {};
    Object.entries(this.tax).forEach(([key, value]) => {
      if (value > "0") {
        tax[key] = value;
      } else if (Array.isArray(value)) {
        tax[key] = value.join(",");
      }
    });

    const lang = getCookie("wp-wpml_current_language");
    const langQuery = lang ? `lang=${lang}` : "";

    this.startLoading();
    return $.ajax({
      url: `/wp-json/wp/v2/${this.getPostType()}?${langQuery}`,
      method: "get",
      data: {
        page: this.page,
        _fields: ["card_html"],
        search:
          this.search_str !== null && this.search_str > ""
            ? this.search_str
            : undefined,
        orderby: this.order_by !== null ? this.order_by : undefined,
        order: this.ascdesc == "asc" ? "asc" : "desc",
        ...tax,
        per_page:
          this.page_size !== null && this.page_size > 0
            ? this.page_size
            : undefined,
        cachebust: new Date().getMinutes(),
      },
    })
      .done((data, _, response) => {
        this.total_pages = Number.parseInt(
          response.getResponseHeader("X-WP-TotalPages") ?? "1"
        );
        this.total_count = response.getResponseHeader("X-WP-Total");
        this.appendCards(data, clear);
        this.updatePostCount();
        this.updatePageTotalCount();
        this.updatePageCount();
      })
      .always(() => {
        this.endLoading();
      });
  }

  private appendCards(data: any, clear: boolean) {
    if (clear) {
      this.getPostContainer().innerHTML = "";
    }
    data.forEach((post: { card_html: string }) => {
      const card = document.createElement("template");
      card.innerHTML = post.card_html?.trim();
      this.getPostContainer().appendChild(card.content.firstChild);
    });
  }

  /**
   * gets the element containing post cards
   * @returns HTMLElement
   */
  protected getPostContainer() {
    if (this.post_container == null) {
      this.post_container = document.querySelector(
        "[post-container]"
      ) as HTMLElement;
    }
    return this.post_container;
  }

  protected updatePostCount = () =>
    this.setElContent("[post-total-count]", this.total_count);
  protected updatePageCount = () =>
    this.setElContent("[post-current-page]", this.page.toString());
  protected updatePageTotalCount = () =>
    this.setElContent("[post-total-pages]", this.total_pages.toString());

  private setElContent(selector: string, html: string) {
    const elements = document.querySelectorAll(selector);
    elements.forEach((el) => {
      el.innerHTML = html;
    });
  }

  protected startLoading() {
    this.toggleBodyClass("loading", true);
  }

  /**
   * This is called after the cards were fetched and appended to container
   */
  protected endLoading() {
    this.toggleBodyClass("loading", false);
    this.toggleBodyClass("all-pages-loaded", this.page >= this.total_pages);
    this.toggleBodyClass(
      "no-posts",
      this.total_count == null || this.total_count == "0"
    );
    this.notifyLoaded();
  }

  private toggleBodyClass(className: string, add: boolean) {
    if (add) {
      document.body.classList.add(className);
    } else {
      document.body.classList.remove(className);
    }
  }

  private getPostType() {
    if (this.post_type == null) {
      this.post_type = (
        document.querySelector(
          "input[type=hidden][name=post_type]"
        ) as HTMLInputElement
      ).value;
    }
    return this.post_type;
  }

  protected shouldLoadFirstPage() {
    return this.getPostContainer()?.childElementCount < 1;
  }

  private notifyLoaded() {
    const e = new CustomEvent("inb-cards-loaded");
    document.dispatchEvent(e);
  }
}

export class PostFiltering extends PostFilteringBase {
  constructor() {
    super();
    this.parseUrl();
  }

  run() {
    this.loadDefaults();
    if (this.shouldLoadFirstPage()) {
      this.initialLoad();
    }
  }

  private initialLoad() {
    if (this.getPostContainer()?.childElementCount == 0) {
      this.fetch();
    }
  }

  private loadDefaults() {
    const container = this.getPostContainer();
    if (container !== null) {
      let order_by = container.getAttribute("post-order-by");
      let page_size = container.getAttribute("post-page-size");
      if (page_size === null) {
        page_size = (
          document.querySelector(
            "input[type=hidden][name=page_size]"
          ) as HTMLInputElement
        )?.value;
      }
      this.order_by_default = order_by !== null ? order_by : "date";
      this.ascdesc_default =
        container.getAttribute("post-order-ascdesc") == "asc" ? "asc" : "desc";
      this.order_by = this.order_by_default;
      this.ascdesc = this.ascdesc_default;
      this.page_size = !isNaN(parseInt(page_size)) ? parseInt(page_size) : null;
    }
  }

  /**
   * Calls the pagination on mouse click
   * @param e MouseEvent
   */
  onPaginateClick(e: MouseEvent) {
    e?.preventDefault();
    e?.stopPropagation();
    this.paginate();
  }

  /**
   * Calls the search on input change
   * The value is pulled from event target (should be input element)
   * @param e Event
   */
  onSearchChange(e: Event) {
    const input = e.target as HTMLInputElement;
    if (input !== null) {
      const doDelay = e.type == "input";
      this.search(input.value, doDelay);
    }
  }

  /**
   * Calls the taxonomy filter on select change
   * The value is pulled from event target (should be select element)
   * @param e Event
   */
  onTaxChange(e: Event) {
    const select = e.target as HTMLSelectElement;
    if (select !== null) {
      this.taxonomy(select.name, select?.value ?? null);
    }
  }

  /**
   * Calls the order by on select change
   * The value is pulled from event target (should be select element)
   * @param e Event
   */
  onOrderByChange(e: Event) {
    const select = e.target as HTMLSelectElement;
    if (select !== null) {
      this.orderBy(select?.value ?? null);
    }
  }

  /**
   * Calls the order sort on select change
   * The value is pulled from event target (should be select element)
   * @param e Event
   */
  onOrderChange(e: Event) {
    const input = e.target as HTMLSelectElement;
    if (input !== null) {
      this.orderBy(null, input?.value == "asc" ? "asc" : "desc");
    }
  }

  protected startLoading() {
    super.startLoading();
    this.updateUrl();
  }

  private updateUrl() {
    const sp = new URLSearchParams(window.location.search);
    if (this.search_str > "") {
      sp.set("search", this.search_str);
    }
    Object.entries(this.tax).forEach(([key, value]) => {
      const val = Array.isArray(value) ? value.join(",") : value;
      if (val !== null && val > "0") {
        sp.set("tax_" + key, val.toString());
      }
    });
    if (this.order_by !== null && this.order_by !== this.order_by_default) {
      sp.set("orderby", this.order_by);
    }
    if (this.ascdesc !== this.ascdesc_default) {
      sp.set("order", this.ascdesc);
    }
    window.history.replaceState({}, null, `?${sp.toString()}`);
  }

  private parseUrl() {
    const sp = new URLSearchParams(location.search);
    sp.forEach((value, key) => {
      switch (key) {
        case "search":
          this.search_str = value;
          break;
        case "orderby":
          this.order_by = value !== null ? value : this.order_by_default;
          break;
        case "order":
          this.ascdesc =
            value !== null
              ? value == "asc"
                ? "asc"
                : "desc"
              : this.ascdesc_default;
          break;
        default:
          if (key.startsWith("tax_")) {
            this.tax[key.replace("tax_", "")] = value.split(",");
          }
          break;
      }
    });
  }
}
