interface CurrentYearMonthObject {
  year?: number;
  month?: number;
}

declare global {
  interface Window {
    wpbsSelectableDates: any;
  }
}

export class WpbsFormExtendDatepicker {
  private $wpbsMainWrapper: JQuery = null;
  private dateFormat: string = "dd.mm.yy";
  // private selectableDates: any = {};
  private startDate: Date = null;
  private endDate: Date = null;
  private maxDate: Date = null;
  private $startDate: JQuery = null;
  private $endDate: JQuery = null;
  private current: CurrentYearMonthObject = {
    year: null,
    month: null,
  };

  constructor($wpbsMainWrapper: JQuery) {
    this.$wpbsMainWrapper = $wpbsMainWrapper;
  }

  public init(): void {
    if (this.$wpbsMainWrapper.find(".wpbs-form-container").length > 0) {
      this.getCurrentYearMonth();
      this.retrieveSelectableDates(this.current.year, this.current.month);
      this.initializeDatepickers();
      this.initializeInputWrapperClicks();
      this.initializeFocusEvents();
      this.observeContainerChanges();
      this.initializeDatesValidationHook();
      this.updateDatepickers();

      $(document).on("wpbs_form_updated", () => {
        this.initializeDatepickers();
        this.updateDatepickers();
      });
    }
  }

  private getCurrentYearMonth() {
    const currDate = new Date();
    this.current.year = currDate.getFullYear();
    this.current.month = currDate.getMonth() + 1;
  }

  private initializeDatepickers() {
    const _this = this;
    const $instance = this.$wpbsMainWrapper.find(".wpbs-form-container .wpbs-form-selected-dates").first();

    if ($instance.hasClass("date-inputs-initialized")) {
      return;
    }

    // get original fields
    const $originalStartDateField = $instance.find(".wpbs-form-field-start-date .wpbs-form-field-input");
    const $originalEndDateField = $instance.find(".wpbs-form-field-end-date .wpbs-form-field-input");

    // hide original fields and append inputs for datepickers
    $originalStartDateField.hide().after("<div class='start-date-input-wrapper'><input type='text' name='start_date_input' class='wpbs-start-date-input' readonly='readonly'/></div>");
    $originalEndDateField.hide().after("<div class='end-date-input-wrapper'><input type='text' name='end_date_input' class='wpbs-end-date-input' readonly='readonly'/></div>");

    this.$startDate = $instance.find(".wpbs-start-date-input").datepicker({
      changeMonth: true,
      changeYear: true,
      dateFormat: _this.dateFormat,
      firstDay: 1,
      minDate: 0,
      beforeShow: function (input: Element, inst: any) {
        $("#ui-datepicker-div").addClass("wpbs-datepicker");
        return inst;
      },
      beforeShowDay: (date: Date): any[] => {
        return _this.showDaySettings(date);
      },
      onSelect(dateText: string, inst: any) {
        _this.startDate = _this.parseDate(dateText);
        _this.endDate = null;
        _this.maxDate = null;
        _this.$endDate.datepicker("option", {
          minDate: _this.startDate,
          defaultDate: null,
          disabled: false,
        });
        _this.simulateCalendarClick(_this.startDate);
      },
      onChangeMonthYear(year: number, month: number) {
        _this.refreshCalendar(year, month);
      },
      onClose: function () {
        $("#ui-datepicker-div").hide().removeClass("wpbs-datepicker");
        setTimeout(function () {
          _this.$endDate.datepicker("show");
        }, 100);
      },
    });

    this.$endDate = $instance
      .find(".wpbs-end-date-input")
      .datepicker({
        changeMonth: true,
        changeYear: false,
        dateFormat: _this.dateFormat,
        firstDay: 1,
        minDate: 0,
        beforeShow: function (input: Element, inst: any) {
          $("#ui-datepicker-div").addClass("wpbs-datepicker");

          return inst;
        },
        beforeShowDay: (date: Date): any[] => {
          if (_this.maxDate && date >= _this.maxDate) {
            return [false, "", ""];
          }

          const settings = _this.showDaySettings(date);

          if (settings[0] === false && date > _this.startDate) {
            _this.maxDate = date;
          }

          return settings;
        },
        onClose: function () {
          $("#ui-datepicker-div").hide().removeClass("wpbs-datepicker");
        },
        onSelect: (dateText: string) => {
          if (_this.endDate !== null && _this.startDate !== null) {
            _this.simulateCalendarClick(_this.startDate);
          }
          _this.endDate = _this.parseDate(dateText);
          _this.$endDate.datepicker("option", {
            defaultDate: _this.endDate,
          });
          _this.simulateCalendarClick(_this.endDate);
        },
        onChangeMonthYear(year: number, month: number) {
          _this.refreshCalendar(year, month);
        },
      })
      .datepicker("option", "disabled", true);

    $instance.addClass("date-inputs-initialized");

    // hide datepicker div
    $("#ui-datepicker-div").hide();
  }

  private simulateCalendarClick(date: Date) {
    const $calendar = this.$wpbsMainWrapper.find(".wpbs-container");

    const y: number = date.getFullYear(),
      m: number = date.getMonth() + 1,
      d: number = date.getDate();

    const $wpbsDate = $calendar.find(`.wpbs-date[data-year="${y}"][data-month="${m}"][data-day="${d}"]`);

    if ($wpbsDate.length) {
      $wpbsDate.trigger("mouseenter").trigger("click");
    }
  }

  private observeContainerChanges() {
    const target = document.querySelector(".wpbs-main-wrapper");
    const config = { attributes: true, childList: true, subtree: true };
    const callback = (mutations: any) => {
      mutations.forEach((mutation: any) => {
        if (mutation.type === "childList") {
          if (mutation.addedNodes.length && mutation.addedNodes[0]?.classList?.contains("wpbs-container")) {
            // remove overlay
            $("#ui-datepicker-div").removeClass("wpbs-loading");
          }
        }
      });
    };

    const observer = new MutationObserver(callback);
    observer.observe(target, config);
  }

  private initializeDatesValidationHook() {
    $(document).ajaxSuccess((event, xhr, settings) => {
      if (settings?.data && xhr.responseText && settings.data.includes("wpbs_validate_date_selection")) {
        this.initializeDatepickers();
        this.updateDatepickers();
      }
    });
  }

  private updateDatepickers() {
    this.updateStartDatepicker();
    this.updateEndDatepicker();
  }

  private updateStartDatepicker() {
    const timeStamp = this.$wpbsMainWrapper.find(".wpbs-container").data("start_date");

    if (timeStamp !== 0 && timeStamp !== this.startDate?.getTime()) {
      // update start
      this.startDate = new Date(timeStamp);
      this.$startDate.datepicker("setDate", this.startDate);
      // update end
      this.endDate = null;
      this.maxDate = null;
      this.$endDate.datepicker("option", {
        minDate: this.startDate,
        disabled: false,
      });
    } else {
      this.$startDate.val(this.formatDate(this.startDate));
    }
  }

  private updateEndDatepicker() {
    const timeStamp = this.$wpbsMainWrapper.find(".wpbs-container").data("end_date");

    if (timeStamp !== 0 && timeStamp !== this.endDate?.getTime()) {
      this.endDate = new Date(timeStamp);
      this.$endDate.datepicker("setDate", this.endDate);
    } else {
      this.$endDate.val(this.formatDate(this.endDate));
    }
  }

  private showDaySettings(date: Date): any[] {
    const y = date.getFullYear(),
      m = date.getMonth() + 1,
      d = date.getDate();

    const dayExists = (_y: number, _m: number, _d: number) => {
      return window.wpbsSelectableDates?.[_y]?.[_m]?.hasOwnProperty(_d);
    };

    if (!dayExists(y, m, d)) {
      this.retrieveSelectableDates(y, m, false);
    }

    if (dayExists(y, m, d)) {
      return [window.wpbsSelectableDates[y][m][d], "", ""];
    } else {
      return [true, "", ""];
    }
  }

  private parseDate(dateText: string) {
    let date;

    try {
      date = $.datepicker.parseDate(this.dateFormat, dateText);
    } catch (error) {
      date = null;
    }

    return date;
  }

  private formatDate(date: Date): string {
    return $.datepicker.formatDate(this.dateFormat, date);
  }

  private retrieveSelectableDates(year: number, month: number, async: boolean = true) {
    const data = {
      action: "inoby_wpbs_get_selectable_dates",
      calendar_id: $(".wpbs-container").data("id"),
      year,
      month,
    };

    $.ajax({
      type: "POST",
      url: window.inoby_vars.ajax.url,
      data: data,
      async: async,
      error: () => {},
      success: (res) => {
        res = JSON.parse(res);
        if (res && res?.success) {
          window.wpbsSelectableDates = this.merge(res.data, window.wpbsSelectableDates);
        }
      },
    });
  }

  private merge(target: any, ...sources: any): any {
    if (!sources.length) return target;
    const source = sources.shift();
    const isObject = (item: any) => {
      return item && typeof item === "object" && !Array.isArray(item);
    };

    if (isObject(target) && isObject(source)) {
      for (const key in source) {
        if (isObject(source[key])) {
          if (!target[key]) Object.assign(target, { [key]: {} });
          this.merge(target[key], source[key]);
        } else {
          Object.assign(target, { [key]: source[key] });
        }
      }
    }

    return this.merge(target, ...sources);
  }

  private refreshCalendar(year: number, month: number) {
    const $calendar = this.$wpbsMainWrapper.find(".wpbs-container").first();
    const currY = Number.parseInt($calendar.data("current_year")),
      currM = Number.parseInt($calendar.data("current_month")),
      visibleMonths = Number.parseInt($calendar.data("months_to_show"));

    if (year !== currY || month < currM || month > currM + visibleMonths - 1) {
      $("#ui-datepicker-div").toggleClass("wpbs-loading");

      if ($calendar.find(".wpbs-select-container select").length === 0) {
        $calendar.append(`
        <div class="wpbs-select-container" style="display: none;">
        <select></select>
        </div>
        `);
      }
      const value = new Date(year, month - 1, 15, 0, 0, 0, 0).getTime() / 1000;
      const $select = $calendar.find(".wpbs-select-container select");

      $select.append(`<option value="${value}"></option>`);
      $select.val(value).trigger("change");
    }
  }

  private initializeInputWrapperClicks() {
    $(document).on("click", ".start-date-input-wrapper", (e) => {
      if ($(e.target).hasClass("wpbs-start-date-input")) {
        return;
      }
      setTimeout(() => {
        this.$startDate.datepicker("show");
        this.repositionDatepicker(this.$startDate.get(0));
      }, 10);
    });

    $(document).on("click", ".end-date-input-wrapper", (e) => {
      if ($(e.target).hasClass("wpbs-end-date-input")) {
        return;
      }
      setTimeout(() => {
        this.$endDate.datepicker("show");
        this.repositionDatepicker(this.$endDate.get(0));
      }, 10);
    });
  }

  private initializeFocusEvents(): void {
    this.$wpbsMainWrapper.on("focus", ".wpbs-start-date-input, .wpbs-end-date-input", (e) => {
      this.repositionDatepicker(e.target);
    });
  }

  /**
   * Reposition div with datepicker in mobile view
   */
  private repositionDatepicker(input: HTMLElement): void {
    if (this.$wpbsMainWrapper.parents(".booking-mobile").length) {
      setTimeout(() => {
        const dp = $("div#ui-datepicker-div");
        const inputRect = input.getBoundingClientRect();
        const dpRect = dp.get(0)?.getBoundingClientRect();
        dp.css({
          top: inputRect.top - dpRect.height,
        });
      }, 50);
    }
  }
}
