import { CalendarDate } from '../../../../../../helpers/calendar';
import { WeekdayLongNames } from '../../../../../../helpers/calendar/utils';
import { VegaInternalTranslation } from '../../../../../../helpers/translation/internal-translation-controller';
import { flatNestedArray } from '../../../../../../utils/array';
import { CalendarEventBaseRepeatPattern } from './base-repeat-pattern';
/** The `CalendarEventWeeklyRepeatPattern` class in TypeScript represents a weekly repeat pattern for calendar events, allowing for matching dates based on specific weekdays and generating future dates with optional constraints. */
export class CalendarEventWeeklyRepeatPattern extends CalendarEventBaseRepeatPattern {
  /**
   * This TypeScript constructor initializes a calendar date range with specified parameters including start date, end date, interval, count, until date, and an array of week days.
   *
   * @param {CalendarDate} startDate - The `startDate` parameter represents the starting date of the
   * calendar event or schedule. It is of type `CalendarDate`, which likely includes the year, month, and day components of a date.
   * @param {number} interval - The `interval` parameter represents the frequency of the recurrence. For
   * example, if `interval` is set to 1, the event will occur every week. If it's set to 2, the event
   * will occur every two weeks.
   * @param {Nullable<number>} count - The `count` parameter specifies the number of occurrences of the rule. If `count` is
   * provided, the recurrence will generate at most that many occurrences. If `count` is not provided,
   * the recurrence will continue indefinitely based on other parameters like `endDate` or `until`.
   * @param {Nullable<CalendarDate>} until - The `until` parameter specifies the end date for the recurrence rule. If provided,
   * the recurrence will continue until this date. If not provided, the recurrence will continue
   * indefinitely based on other parameters like `count` or `interval`.
   * @param {Nullable<VegaCalendarWeekDayType[]>} byWeekDay - The `byWeekDay` parameter is an array of
   * `VegaCalendarWeekDayType` values, which represent the days of the week for which the event should occur.
   */
  constructor(startDate, interval, count, until, byWeekDay) {
    super(startDate, interval, count, until);
    this.byWeekDay = this.sortWeekDay(byWeekDay);
    this.initCache();
  }
  /**
   * The function `getRepeatSeries` retrieves a series of repeated events within a specified calendar period.
   *
   * @param {CalendarPeriod} period - The `period` parameter in the `getRepeatSeries` method represents
   * a specific time period in the form of a `CalendarPeriod` object. This object typically contains a
   * `startDate` and an `endDate`, which define the range of dates for which you want to generate repeat series.
   * @param {number} eventSpansDays - the current repeat series cross days, default value is one
   * @returns {CalendarDate[]} repeat start date array
   */
  getRepeatSeries(period, eventSpansDays = 1) {
    this.generateRepeatDateCacheUntilEndDate(period.endDate);
    return this.getValidCache().filter((item) => {
      return this.isSeriesInPeriod(item, eventSpansDays, period);
    });
  }
  /**
   * Generate repeat display string.
   *
   * @returns {string} repeat display string
   */
  generateRepeatString() {
    const weekDays = this.byWeekDay.map((weekDay) => VegaInternalTranslation.t(WeekdayLongNames[weekDay]));
    const repeatString = this.interval > 1
      ? VegaInternalTranslation.t('Repeat every {0} weeks on {1}', this.interval, weekDays.join(', '))
      : VegaInternalTranslation.t('Repeat weekly on {0}', weekDays.join(', '));
    return repeatString + this.generateRepeatEndsString();
  }
  /**
   * This function generates repeated dates and caches them until a specified end date.
   *
   * @param {CalendarDate} endDate - The `endDate` parameter is a `CalendarDate` object that represents
   * the date until which the repeat date cache needs to be generated. The function
   * `generateRepeatDateCacheUntilEndDate` generates repeat dates until this specified end date.
   */
  generateRepeatDateCacheUntilEndDate(endDate) {
    const lastMatchDate = this.cache[this.cache.length - 1];
    if (lastMatchDate[0].toDate() < endDate.toDate() &&
      (!this.count || (typeof this.count === 'number' && this.cache.length < this.count))) {
      let nextMatchDate = this.getNextRepeatStartDate(lastMatchDate);
      while (nextMatchDate && nextMatchDate[0].toDate() < endDate.toDate()) {
        const newNextMatchDate = this.getNextRepeatStartDate(nextMatchDate);
        //The below if block is used to break the infinite loop when the `getNextRepeatStartDate` method return unexpected result. such as when the date is greater than a certain value, it will be returned back 1900.
        if (newNextMatchDate &&
          newNextMatchDate[0] &&
          newNextMatchDate[0].toDate() <= nextMatchDate[0].toDate()) {
          this.cache.pop();
          break;
        }
        nextMatchDate = newNextMatchDate;
        if (typeof this.count === 'number' && this.getValidCache().length >= this.count) {
          break;
        }
      }
    }
  }
  /**
   * The function getNextRepeatStartDate calculates the next start dates based on a given array of CalendarDate objects and returns them if certain conditions are met.
   *
   * @param {CalendarDate[]} dates - The `dates` parameter is an array of `CalendarDate` objects.
   * @returns {Nullable<CalendarDate[]>} The `getNextRepeatStartDate` function returns a nullable array of `CalendarDate` objects.
   * If there are no next dates available or if the cache count exceeds a specified limit, it returns
   * `null`. Otherwise, it returns an array of `CalendarDate` objects representing the next start dates.
   */
  getNextRepeatStartDate(dates) {
    const nextDate = this.getNextStartDates(dates);
    if (nextDate.length === 0 ||
      (typeof this.count === 'number' && this.getValidCache().length >= this.count)) {
      return null;
    }
    else {
      const nextCalendarDates = nextDate.map((item) => CalendarDate.from(item));
      this.cache.push(nextCalendarDates);
      return nextCalendarDates;
    }
  }
  /**
   * The function `getNextStartDates` takes an array of `CalendarDate` objects, calculates the next dates based on a specified interval, and filters out dates that are after a specified end date.
   *
   * @param {CalendarDate[]} dates - The `dates` parameter is an array of `CalendarDate` objects
   * representing specific dates in a calendar. Each `CalendarDate` object has properties for the year,
   * month, and date.
   * @returns {Date[]} The `getNextStartDates` function returns an array of `Date` objects representing the next
   * start dates based on the input `CalendarDate` array. The function calculates the next start dates
   * by adding a specific interval (multiplied by 7 to represent weeks) to each date in the input array.
   * Finally, it filters out any dates that are after a specified `until` date, if provided.
   */
  getNextStartDates(dates) {
    const nextDates = dates.map((dateItem) => new Date(dateItem.year, dateItem.month - 1, dateItem.date + this.interval * 7));
    return nextDates.filter((item) => !this.until || item <= this.until.toDate());
  }
  /**
   * The `initCache` function initializes a cache of calendar dates based on the start date and weekly repeat pattern.
   */
  initCache() {
    const result = [];
    const currentWeekDayIndex = this.getCurrentDayIndex(this.repeatStartDate);
    this.byWeekDay.forEach((day) => {
      const index = CalendarEventWeeklyRepeatPattern.weekDay.indexOf(day);
      const days = index - currentWeekDayIndex;
      const { year, month, date } = this.repeatStartDate;
      const dateItem = CalendarDate.from(new Date(year, month - 1, date + days));
      if (!this.until || dateItem.toDate() <= this.until.toDate()) {
        result.push(dateItem);
      }
    });
    this.cache = [result];
  }
  /**
   * The function `sortWeekDay` sorts an array of week days based on their position in a predefined list.
   *
   * @param {boolean} weekDay - The `weekDay` parameter is an array of week day values that need to be sorted in a
   * specific order. The function `sortWeekDay` takes this array and sorts it based on a predefined
   * order specified in `CalendarEventWeeklyRepeatPattern.weekDay`.
   * @returns {VegaCalendarWeekDayType[]} The `sortWeekDay` function is returning the `weekDay` array sorted based on the index of
   * each `VegaCalendarWeekDayType` item in the `CalendarEventWeeklyRepeatPattern.weekDay` array.
   */
  sortWeekDay(weekDay) {
    if (weekDay && weekDay.length) {
      return weekDay
        .map((item) => item.toLocaleLowerCase())
        .sort((itemA, itemB) => {
        return (CalendarEventWeeklyRepeatPattern.weekDay.indexOf(itemA) -
          CalendarEventWeeklyRepeatPattern.weekDay.indexOf(itemB));
      });
    }
    else {
      return [CalendarEventWeeklyRepeatPattern.weekDay[this.getCurrentDayIndex(this.repeatStartDate)]];
    }
  }
  /**
   * This function returns the index of the current day in a week, with Sunday represented as 0.
   *
   * @param {CalendarDate} date - The `date` parameter in the `getCurrentDayIndex` function is of type
   * `CalendarDate`, which is a custom data type representing a date in a calendar.
   * @returns {number} The function `getCurrentDayIndex` returns the index of the current day in the week, with
   * Sunday being represented as 0 and Saturday being represented as 6.
   */
  getCurrentDayIndex(date) {
    return date.toDate().getDay();
  }
  /**
   * The function `getValidCache` returns a filtered and concatenated array of CalendarDate objects from the cache based on a start date.
   *
   * @returns {CalendarDate[]} The `getValidCache` method returns an array of `CalendarDate` objects that meet the
   * condition of having a date greater than or equal to the `startDate` property. The method first
   * flattens a nested array of `CalendarDate` objects stored in the `cache` property, then filters out
   * the `CalendarDate` objects that do not meet the condition, and finally concatenates the filtered
   * array
   */
  getValidCache() {
    const flatArr = flatNestedArray(this.cache.slice(1));
    return this.cache[0]
      .filter((item) => item.toDate() >= this.repeatStartDate.toDate())
      .concat(flatArr)
      .slice(0, typeof this.count === 'number' ? this.count : undefined);
  }
}
CalendarEventWeeklyRepeatPattern.weekDay = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map((item) => item.toLowerCase());
