(function ($) {
  var todayDate = new Date();
  todayDate.setHours(0, 0, 0, 0);

  function getShiftedDate(date, timeZone) {
    if (!timeZone) return date;
    const parts = new Intl.DateTimeFormat("en-US", {
      timeZone,
      year: "numeric",
      month: "numeric",
      day: "numeric",
      hour: "numeric",
      minute: "numeric",
      second: "numeric",
    }).formatToParts(date);
    const p = parts.reduce((acc, part) => {
      acc[part.type] = part.value;
      return acc;
    }, {});
    return new Date(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
  }

  var monthMap = {
    1: "january",
    2: "february",
    3: "march",
    4: "april",
    5: "may",
    6: "june",
    7: "july",
    8: "august",
    9: "september",
    10: "october",
    11: "november",
    12: "december",
  };

  var dayMap = {
    0: "sunday",
    1: "monday",
    2: "tuesday",
    3: "wednesday",
    4: "thursday",
    5: "friday",
    6: "saturday",
  };

  var alternateDayMap = {
    1: "monday",
    2: "tuesday",
    3: "wednesday",
    4: "thursday",
    5: "friday",
    6: "saturday",
    7: "sunday",
  };

  var defaults = {
    date: null,
    weekDayLength: 1,
    prevButton: "Prev",
    nextButton: "Next",
    monthYearOrder: "my",
    monthYearSeparator: " ",
    onClickDate: function (date) { },
    onTimezoneChange: function (timezone) { }, // New callback
    onChangeMonth: function (date) { },
    onClickToday: function (date) { },
    onClickMonthNext: function (date) { },
    onClickMonthPrev: function (date) { },
    onClickYearNext: function (date) { },
    onClickYearPrev: function (date) { },
    onShowYearView: function (date) { },
    onSelectYear: function (date) { },
    customDateProps: function (date) {
      return { classes: "", data: {} };
    },
    customDateHeaderProps: function (weekDay) {
      return { classes: "", data: {} };
    },
    customWeekProps: function (weekNo) {
      return { classes: "", data: {} };
    },
    showThreeMonthsInARow: true,
    enableMonthChange: true,
    enableYearView: true,
    showTodayButton: true,
    highlightSelectedWeekday: true,
    highlightSelectedWeek: true,
    todayButtonContent: "Today",
    showYearDropdown: false,
    min: null,
    max: null,
    disable: function (date) { },
    startOnMonday: false,
    monthMap,
    dayMap,
    alternateDayMap,
    enableTimezone: true,
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
  };

  function CalendarInstance(element, options) {
    this.el = element;
    this.settings = $.extend({}, defaults, options);
    this.yearView = false;
    this.selectedDate = null;
    this.todayDate = new Date();
    this.todayDate.setHours(0, 0, 0, 0);

    if (this.settings.startOnMonday) {
      this.settings.dayMap = this.settings.alternateDayMap;
    }
    if (this.settings.min) {
      this.settings.min = new Date(this.settings.min);
      this.settings.min.setHours(0, 0, 0, 0);
    }
    if (this.settings.max) {
      this.settings.max = new Date(this.settings.max);
      this.settings.max.setHours(0, 0, 0, 0);
    }

    if (this.settings.date) {
      if (typeof this.settings.date === "string") {
        this.selectedDate = new Date(this.settings.date);
      } else {
        this.selectedDate = this.settings.date;
      }
      this.selectedDate.setHours(0, 0, 0, 0);
      this.currentDate = this.selectedDate;
    } else {
      if (this.settings.enableTimezone) {
        this.currentDate = getShiftedDate(new Date(), this.settings.timezone);
        this.todayDate = this.currentDate;
        this.todayDate.setHours(0, 0, 0, 0);
        this.settings.min = this.todayDate;
      } else {
        this.currentDate = this.todayDate;
      }
    }

    this.initEvents();
    this.render();
  }

  CalendarInstance.prototype.update = function (options) {
    this.settings = $.extend(this.settings, options);
    if (options.date) {
      if (typeof options.date === "string") {
        this.selectedDate = new Date(options.date);
      } else {
        this.selectedDate = options.date;
      }
      this.selectedDate.setHours(0, 0, 0, 0);
      this.currentDate = this.selectedDate;
    }
    this.render();
  };

  CalendarInstance.prototype.render = function () {
    var monthData = this.generateMonthData(this.currentDate);
    this.el.html(this.generateDomString(monthData, this.currentDate));

    if (this.settings.highlightSelectedWeekday) {
      this.highlightDays();
    }
    if (this.settings.highlightSelectedWeek) {
      this.highlightWeek();
    }
  };

  CalendarInstance.prototype.getFirstDayOfMonth = function (currentDate) {
    var thisDate =
      currentDate.getMonth() + 1 + "/1/" + currentDate.getFullYear();
    return new Date(thisDate);
  };

  CalendarInstance.prototype.getLastDayOfMonth = function (currentDate) {
    return new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
  };

  CalendarInstance.prototype.getLastMonthLastDay = function (currentDate) {
    var month = currentDate.getMonth();
    var year = currentDate.getFullYear();
    if (month === 0) {
      year -= 1;
      month = 12;
    }
    return new Date(year, month, 0);
  };

  CalendarInstance.prototype.generateWeekData = function (currentDate, weekNo) {
    var firstDay = this.getFirstDayOfMonth(currentDate);
    var firstDayDate = firstDay.getDate();
    var lastDay = this.getLastDayOfMonth(currentDate);
    var lastDayFromLastMonth = this.getLastMonthLastDay(currentDate).getDate();
    var days = [];

    var daysFromLastMonth = firstDay.getDay();
    if (this.settings.startOnMonday) {
      daysFromLastMonth = daysFromLastMonth - 1;
    }
    var daysFromNextMonth = 1;

    if (weekNo === 1) {
      var dayFromLastMonth =
        daysFromLastMonth - 1 < 0
          ? 6 + daysFromLastMonth
          : daysFromLastMonth - 1;
      if (dayFromLastMonth < 6) {
        for (daysFromLastMonth; dayFromLastMonth >= 0; dayFromLastMonth--) {
          var dateObj = new Date(
            currentDate.getFullYear(),
            currentDate.getMonth() - 1,
            lastDayFromLastMonth - dayFromLastMonth
          );
          days.push(dateObj);
        }
      }

      var daysLength = 7 - days.length;
      for (var monthDate = 0; monthDate < daysLength; monthDate++) {
        var dateObj = new Date(
          firstDay.getFullYear(),
          firstDay.getMonth(),
          firstDayDate + monthDate
        );
        days.push(dateObj);
      }
    } else {
      var startWeekFrom =
        (weekNo - (daysFromLastMonth < 0 ? 2 : 1)) * 7 - daysFromLastMonth;
      for (var i = 1; i <= 7; i++) {
        if (startWeekFrom + i <= lastDay) {
          var dateObj = new Date(
            currentDate.getFullYear(),
            currentDate.getMonth(),
            startWeekFrom + i
          );
          days.push(dateObj);
        } else {
          var dateObj = new Date(
            currentDate.getFullYear(),
            currentDate.getMonth() + 1,
            daysFromNextMonth++
          );
          days.push(dateObj);
        }
      }
    }
    return days;
  };

  CalendarInstance.prototype.generateMonthData = function (currentDate) {
    var lastMonthLast = this.getLastDayOfMonth(currentDate).getDate();
    var lastDayFromMonth = this.getLastDayOfMonth(currentDate).getDate();
    var weeks = parseInt(lastMonthLast / 7) + 1;

    var monthData = [];
    for (var weekNo = 1; weekNo <= weeks; weekNo++) {
      monthData.push(this.generateWeekData(currentDate, weekNo));
    }
    var lastBlock = monthData[monthData.length - 1];
    var lastDayInBlock = lastBlock[lastBlock.length - 1].getDate();

    if (
      lastDayInBlock < lastDayFromMonth &&
      lastDayFromMonth - lastDayInBlock < 7
    ) {
      monthData.push(this.generateWeekData(currentDate, weekNo));
    }
    return monthData;
  };

  CalendarInstance.prototype.generateTodayButton = function () {
    return (
      "" +
      '<div class="special-buttons">' +
      '<button class="today-button">' +
      this.settings.todayButtonContent +
      "</button>" +
      "</div>"
    );
  };

  CalendarInstance.prototype.generateYearHeaderDOM = function (currentDate) {
    var str =
      "" +
      '<div class="buttons-container">' +
      (this.settings.enableMonthChange && this.settings.enableYearView
        ? '<button class="prev-button">' + this.settings.prevButton + "</button>"
        : "") +
      '<span class="label-container year-label">';
    if (this.settings.showYearDropdown) {
      str += "" + '<select class="year-dropdown">';
      for (var i = 1970; i < 2117; i++) {
        if (i === currentDate.getFullYear()) {
          str +=
            '<option selected="selected" value="' + i + '">' + i + "</option>";
        } else {
          str += '<option value="' + i + '">' + i + "</option>";
        }
      }
      str += "</select>";
    } else {
      str += currentDate.getFullYear();
    }
    str +=
      "</span>" +
      (this.settings.enableMonthChange && this.settings.enableYearView
        ? '<button class="next-button">' + this.settings.nextButton + "</button>"
        : "") +
      "</div>";

    if (this.settings.enableTimezone) {
      str += '<div class="timezone-container">';
      str += '<select class="form-select timezone-dropdown">';
      const timezones = Intl.supportedValuesOf("timeZone");
      timezones.forEach((tz) => {
        if (tz === this.settings.timezone) {
          str += `<option value="${tz}" selected>${tz}</option>`;
        } else {
          str += `<option value="${tz}">${tz}</option>`;
        }
      });
      str += "</select>";
      str += "</div>";
    }

    return str;
  };

  CalendarInstance.prototype.generateMonthDOM = function (currentDate) {
    var str = "";
    str += '<div class="months-wrapper">';

    for (var month in this.settings.monthMap) {
      if (this.settings.monthMap.hasOwnProperty(month)) {
        var showThreeMonthsInARow = "";
        showThreeMonthsInARow = this.settings.showThreeMonthsInARow
          ? " one-third"
          : "";
        str +=
          '<span class="month' +
          showThreeMonthsInARow +
          '" data-month="' +
          month +
          '" data-year="' +
          currentDate.getFullYear() +
          '"><span>' +
          this.settings.monthMap[month] +
          "</span></span>";
      }
    }

    str += "</div>";
    return str;
  };

  CalendarInstance.prototype.generateMonthHeaderDOM = function (currentDate) {
    let monthYearLabels = [];
    let monthYearLabel = '';

    for (var i of this.settings.monthYearOrder.toLowerCase()) {
      switch (i) {
        case 'm':
          monthYearLabels.push(
            '<span class="month-label full-head-1">' +
            this.settings.monthMap[currentDate.getMonth() + 1] +
            "</span>"
          );
          break;

        case 'y':
          monthYearLabels.push(
            '<span class="year-label">' +
            currentDate.getFullYear() +
            "</span>"
          );
          break;
      }
    }

    monthYearLabel = monthYearLabels.join(this.settings.monthYearSeparator);

    let timezoneSelect = "";
    if (this.settings.enableTimezone && !this.yearView) {
      timezoneSelect += '<div class="timezone-container" style="margin-bottom: 10px;">';
      timezoneSelect += '<select class="form-select timezone-dropdown">';
      const timezones = Intl.supportedValuesOf("timeZone");
      timezones.forEach((tz) => {
        if (tz === this.settings.timezone) {
          timezoneSelect += `<option value="${tz}" selected>${tz}</option>`;
        } else {
          timezoneSelect += `<option value="${tz}">${tz}</option>`;
        }
      });
      timezoneSelect += "</select>";
      timezoneSelect += "</div>";
    }

    return (
      "" +
      timezoneSelect +
      '<div class="buttons-container">' +
      (this.settings.enableMonthChange
        ? '<button class="prev-button">' + this.settings.prevButton + "</button>"
        : "") +
      '<span class="label-container month-container">' +
      monthYearLabel +
      "</span>" +
      (this.settings.enableMonthChange
        ? '<button class="next-button">' + this.settings.nextButton + "</button>"
        : "") +
      "</div>"
    );
  };

  CalendarInstance.prototype.generateWeekHeaderDOM = function (currentDate) {
    const { classes: weekClasses, data: weekData } =
      this.settings.customWeekProps(0);
    let dataStr = "";
    if (Object.keys(weekData)) {
      Object.keys(weekData).forEach((item) => {
        dataStr += ` data-${item}="${weekData[item]}" `;
      });
    }

    var str = "";
    str += '<div class="bkinz-weeks-wrapper header">';
    str +=
      '<div class="bkinz-week' +
      (this.settings.startOnMonday ? " start-on-monday" : "") +
      (weekClasses ? " " + weekClasses : "") +
      '" data-week-no="' +
      0 +
      '"' +
      dataStr +
      ">";

    for (var weekDay in this.settings.dayMap) {
      if (this.settings.dayMap.hasOwnProperty(weekDay)) {
        const { classes, data } = this.settings.customDateHeaderProps(weekDay);
        let dataStr = "";
        if (Object.keys(data)) {
          Object.keys(data).forEach((item) => {
            dataStr += ` data-${item}="${data[item]}" `;
          });
        }
        str +=
          '<div class="bkinz-day-full header' +
          (classes ? " " + classes : "") +
          '" data-day="' +
          weekDay +
          '"' +
          dataStr +
          "  >" +
          (typeof this.settings.formatWeekDay === "function"
            ? this.settings.formatWeekDay(weekDay)
            : this.settings.dayMap[weekDay].substring(0, this.settings.weekDayLength)) +
          "</div>";
      }
    }

    str += "</div>";
    str += "</div>";
    return str;
  };

  CalendarInstance.prototype.generateWeekDOM = function (monthData, currentDate) {
    var str = "";
    var self = this;
    str += '<div class="bkinz-weeks-wrapper">';

    monthData.forEach(function (week, weekNo) {
      const { classes, data } = self.settings.customWeekProps(weekNo);
      let dataStr = "";
      if (Object.keys(data)) {
        Object.keys(data).forEach((item) => {
          dataStr += ` data-${item}="${data[item]}" `;
        });
      }
      str +=
        '<div class="bkinz-week' +
        (self.settings.startOnMonday ? " start-on-monday" : "") +
        (classes ? " " + classes : "") +
        '" data-week-no="' +
        (weekNo + 1) +
        '"' +
        dataStr +
        ">";

      week.forEach(function (day, dayNo) {
        var disabled = false;
        if (day.getMonth() !== currentDate.getMonth()) disabled = true;
        disabled = disabled ? " disabled" : "";

        var selected = false;
        if (self.selectedDate) {
          if (day == self.selectedDate.toString()) selected = true;
          selected = selected ? " selected" : "";
        } else {
          selected = "";
        }

        var today = false;

        if (day == self.todayDate.toString()) today = true;
        today = today ? " today" : "";

        var dateDisabled = "data-ola";
        if (
          (self.settings.min && self.settings.min > day) ||
          (self.settings.max && self.settings.max < day) ||
          (self.settings.disable &&
            typeof self.settings.disable === "function" &&
            self.settings.disable(day))
        ) {
          dateDisabled = 'data-disabled="disabled" ';
        }

        const { classes, data } = self.settings.customDateProps(day);
        let dataStr = "";
        if (Object.keys(data)) {
          Object.keys(data).forEach((item) => {
            dataStr += ` data-${item}="${data[item]}" `;
          });
        }

        str +=
          '<div class="bkinz-day-full' +
          disabled +
          selected +
          today +
          (classes ? " " + classes : "") +
          '" data-date="' +
          day.getTime() +
          '" ' +
          dataStr +
          dateDisabled +
          " ><span>" +
          (typeof self.settings.formatDate === "function"
            ? self.settings.formatDate(day)
            : day.getDate()) +
          "</span></div>";
      });

      str += "</div>";
    });
    str += "</div>";
    return str;
  };

  CalendarInstance.prototype.generateDomString = function (monthData, currentDate) {
    var calendarDump = "";

    calendarDump += '<div class="calendar-box">';

    if (this.yearView) {
      calendarDump += '<div class="months-container">';

      calendarDump += this.generateYearHeaderDOM(currentDate);

      calendarDump += this.generateMonthDOM(currentDate);

      calendarDump += "</div>";
    } else {
      calendarDump += '<div class="weeks-container">';

      calendarDump += this.generateMonthHeaderDOM(currentDate);

      calendarDump += this.generateWeekHeaderDOM(currentDate);

      calendarDump += this.generateWeekDOM(monthData, currentDate);

      calendarDump += "</div>";
    }

    if (this.settings.showTodayButton) {
      calendarDump += this.generateTodayButton();
    }

    calendarDump += "</div>";

    return calendarDump;
  };

  CalendarInstance.prototype.highlightDays = function () {
    var selectedElement = this.el.find(".selected");
    var self = this;

    if (selectedElement.length > 0) {
      var date = new Date(selectedElement.data("date"));
      var weekDayNo = date.getDay();

      this.el.find(".bkinz-week").each(function (i, elm) {
        $(elm)
          .find(
            ".bkinz-day-full:eq(" + (weekDayNo - (self.settings.startOnMonday ? 1 : 0)) + ")"
          )
          .addClass("highlight");
      });
    }
  };

  CalendarInstance.prototype.highlightWeek = function () {
    this.el.find(".selected").parents(".bkinz-week").addClass("highlight");
  };

  CalendarInstance.prototype.initEvents = function () {
    var self = this;

    if (this.settings.enableTimezone) {
      this.el.off("change", ".timezone-dropdown").on(
        "change",
        ".timezone-dropdown",
        function (e) {
          self.settings.timezone = $(this).val();
          var now = new Date();
          var shifted = getShiftedDate(now, self.settings.timezone);
          self.todayDate = shifted;
          self.todayDate.setHours(0, 0, 0, 0);
          self.settings.min = self.todayDate;

          self.selectedDate = self.todayDate;
          self.currentDate = self.todayDate;

          self.render();

          if (self.settings.onTimezoneChange) {
            self.settings.onTimezoneChange.call(this, self.settings.timezone, self.currentDate);
          }
        }
      );
    }

    if (this.settings.enableMonthChange) {
      this.el.off("click", ".weeks-container .prev-button").on(
        "click",
        ".weeks-container .prev-button",
        function (e) {
          self.currentDate = new Date(
            self.currentDate.getFullYear(),
            self.currentDate.getMonth() - 1,
            1
          );
          self.settings.onClickMonthPrev(self.currentDate);
          self.render();
        }
      );

      this.el.off("click", ".weeks-container .next-button").on(
        "click",
        ".weeks-container .next-button",
        function (e) {
          self.currentDate = new Date(
            self.currentDate.getFullYear(),
            self.currentDate.getMonth() + 1,
            1
          );
          self.settings.onClickMonthNext(self.currentDate);
          self.render();
        }
      );
    }

    this.el.off("click", ".bkinz-day-full").on("click", ".bkinz-day-full", function (e) {
      var date = new Date($(this).data("date"));
      var isDisabled = $(this).attr("data-disabled") === "disabled";
      if (isDisabled) return;
      self.settings.onClickDate.call(this, date);
    });

    if (this.settings.enableMonthChange && this.settings.enableYearView) {
      this.el.off("click", ".month-container").on(
        "click",
        ".month-container",
        function (e) {
          self.yearView = true;
          self.currentDate = new Date(self.currentDate.getFullYear(), 0, 1);
          self.settings.onShowYearView(self.currentDate);
          self.render();
        }
      );

      this.el.off("click", ".months-container .month").on(
        "click",
        ".months-container .month",
        function (e) {
          var monthEl = $(this);
          var month = monthEl.data("month");
          var year = monthEl.data("year");
          var selectedMonth = new Date(year, month - 1, 1);
          self.settings.onChangeMonth(selectedMonth);

          self.currentDate = selectedMonth;
          self.yearView = false;

          self.render();
        }
      );

      this.el.off("click", ".months-container .prev-button").on(
        "click",
        ".months-container .prev-button",
        function (e) {
          self.currentDate = new Date(self.currentDate.getFullYear() - 1, 0, 1);
          self.settings.onClickYearPrev(self.currentDate);
          self.settings.onSelectYear(self.currentDate);
          self.render();
        }
      );

      this.el.off("click", ".months-container .next-button").on(
        "click",
        ".months-container .next-button",
        function (e) {
          self.currentDate = new Date(self.currentDate.getFullYear() + 1, 0, 1);
          self.settings.onClickMonthNext(self.currentDate);
          self.settings.onSelectYear(self.currentDate);
          self.render();
        }
      );

      this.el.off("change", ".months-container .year-dropdown").on(
        "change",
        ".months-container .year-dropdown",
        function (e) {
          var year = $(this).val();
          self.currentDate = new Date(year, 0, 1);
          self.settings.onSelectYear(self.currentDate);
          self.render();
        }
      );
    }

    if (this.settings.showTodayButton) {
      this.el.off("click", ".today-button").on(
        "click",
        ".today-button",
        function (e) {
          self.currentDate = self.todayDate;
          self.selectedDate = self.todayDate;
          self.settings.onClickToday(self.todayDate);
          self.settings.onClickDate.call(this, self.todayDate);
          self.yearView = false;
          self.render();
        }
      );
    }
  };

  $.fn.updateCalendarOptions = function (options) {
    this.each(function () {
      var instance = $(this).data("calendar-instance");
      if (instance) {
        instance.update(options);
      }
    });
  };

  $.fn.calendar = function (options) {
    var firstInstance;
    this.each(function () {
      var $el = $(this);
      var instance = $el.data("calendar-instance");
      if (!instance) {
        instance = new CalendarInstance($el, options);
        $el.data("calendar-instance", instance);
      } else if (options) {
        instance.update(options);
      }
      if (!firstInstance) {
        firstInstance = instance;
      }
    });

    this.getSelectedDate = function () {
      return firstInstance ? firstInstance.selectedDate : null;
    };

    this.getTimezone = function () {
      return firstInstance ? firstInstance.settings.timezone : null;
    };

    return this;
  };
})(jQuery);
