import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import tz from 'dayjs/plugin/timezone';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import { getIntlDateTimeFormatFromCache } from '../utils';
import { cap } from '../../../utils';
import { CALENDAR_TYPES, CALENDAR_TYPE_LOCALES } from './local';
dayjs.extend(utc);
dayjs.extend(tz);
dayjs.extend(customParseFormat);
export const getDayOfWeek = (date, calendarType = CALENDAR_TYPES.ISO_8601, timeZone) => {
    const weekday = dayjs(date).tz(timeZone).day();
    switch (calendarType) {
        case CALENDAR_TYPES.ISO_8601:
            // Shifts days of the week so that Monday is 0, Sunday is 6
            return (weekday + 6) % 7;
        case CALENDAR_TYPES.ARABIC:
            return (weekday + 1) % 7;
        case CALENDAR_TYPES.HEBREW:
        case CALENDAR_TYPES.US:
        case CALENDAR_TYPES.JP:
            return weekday;
        default:
            throw new Error('Unsupported calendar type.');
    }
};
export const getWeekdays = (calendarType) => {
    const date = new Date();
    const beginOfMonth = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));
    const year = beginOfMonth.getUTCFullYear();
    const monthIndex = beginOfMonth.getUTCMonth();
    return Array.from({ length: 7 }, (v, weekDay) => {
        return new Date(year, monthIndex, weekDay + 1 - getDayOfWeek(beginOfMonth, calendarType, 'UTC'));
    });
};
// returns object with properties for localized days names
// properties are numbers corresponding to days order according to ISO_8601 calendar (1-Monday, 2-Tuesday etc.)
export function getDayOfWeekNamesMap(localeTag) {
    return Object.fromEntries(getWeekdays(CALENDAR_TYPES.ISO_8601).map((dayDate, index) => [
        index + 1,
        dayDate.toLocaleString(localeTag, {
            weekday: 'long'
        })
    ]));
}
const getDateWithCorrectYear = (initialDate, year) => {
    const dateObj = new Date(initialDate);
    // 2 digits years are converted to 19XX by Date constructors
    // setting year value directly to ensure it is correct
    if (!Number.isNaN(dateObj.getTime()) && year < 100)
        dateObj.setUTCFullYear(year);
    return dateObj;
};
// https://weeknumber.net/how-to/javascript
export const getWeek = (date, calendarType = CALENDAR_TYPES.ISO_8601, timeZone) => {
    let result = dayjs(date).tz(timeZone).utc().startOf('day');
    // CALENDAR_TYPES.ISO_8601 - Thursday in current week decides the year.
    // CALENDAR_TYPES.US - Sunday in current week decides the year.
    const days = calendarType === CALENDAR_TYPES.US ? 6 : 3;
    result = result.date(result.date() + days - getDayOfWeek(date, calendarType, timeZone));
    // CALENDAR_TYPES.ISO_8601 - January 4 is always in week 1.
    // CALENDAR_TYPES.US - January 1 is always in week 1.
    const week1 = dayjs
        .utc()
        .year(result.year())
        .month(0)
        .date(calendarType === CALENDAR_TYPES.US ? 1 : 4)
        .startOf('day');
    // Adjust to Thursday in week 1 and count number of weeks from date to week1.
    return (1 +
        Math.round(((result.toDate().getTime() - week1.toDate().getTime()) / 86400000 -
            days +
            getDayOfWeek(week1, calendarType, 'UTC')) /
            7));
};
// https://weeknumber.net/how-to/javascript
export const getWeekYear = (date, calendarType = CALENDAR_TYPES.ISO_8601, timeZone) => {
    let result = dayjs(date).tz(timeZone).utc();
    // CALENDAR_TYPES.ISO_8601 - Thursday in current week decides the year.
    // CALENDAR_TYPES.US - Sunday in current week decides the year.
    const days = calendarType === CALENDAR_TYPES.US ? 6 : 3;
    result = result.date(result.date() + days - getDayOfWeek(date, calendarType, timeZone));
    return result.year();
};
export const getCalendarTypeFromLocale = (localeTag) => {
    const calendarType = Object.keys(CALENDAR_TYPE_LOCALES).find(type => CALENDAR_TYPE_LOCALES[type].includes(localeTag ?? navigator.language));
    return calendarType || CALENDAR_TYPES.ISO_8601;
};
export const getDateOfISOWeek = (week, year) => {
    const dateOfWeek = getDateWithCorrectYear(Date.UTC(year, 0, 1 + (week - 1) * 7), year);
    const dayOfWeek = dateOfWeek.getUTCDay();
    const weekStartISO = dateOfWeek;
    if (dayOfWeek <= 4)
        weekStartISO.setUTCDate(dateOfWeek.getUTCDate() - dayOfWeek + 1);
    else
        weekStartISO.setUTCDate(dateOfWeek.getUTCDate() + 8 - dayOfWeek);
    return weekStartISO;
};
const timeRegex = /^T?\d{2}:\d{2}/;
export const parseToDate = (value) => {
    let result = new Date(value);
    if (value && typeof value === 'string') {
        let [year, month, day, hour, minute, second, millisecond] = [1970, 1, 1, 0, 0, 0, 0];
        if (timeRegex.test(value)) {
            [hour, minute, second = 0, millisecond = 0] = value
                .toString()
                .slice(Number(value.startsWith('T')))
                .split(/[.:/]/)
                .map(Number);
        }
        else {
            [year, month, day = 0, hour = 0, minute = 0, second = 0, millisecond = 0] = value
                .toString()
                .split(/[.: TZ+-]/)
                .map(Number);
        }
        result = getDateWithCorrectYear(Date.UTC(year, month - 1, day, hour, minute, second, millisecond), year);
    }
    else if (typeof value === 'number') {
        result = new Date(value);
    }
    return result;
};
export function getQuarter(date) {
    const parsedDate = parseToDate(date);
    const quarter = [1, 2, 3, 4];
    return quarter[Math.floor(parsedDate.getUTCMonth() / 3)];
}
export function getQuarterForDateTimeZone(date, timeZone) {
    const quarter = [1, 2, 3, 4];
    return quarter[Math.floor(dayjs(date).tz(timeZone).month() / 3)];
}
export const getDateTimeParts = (localeTag, options, labelFormat) => {
    return getIntlDateTimeFormatFromCache(localeTag, options)
        .formatToParts()
        .map(({ type, value }) => {
        switch (type) {
            case 'day':
            case 'month':
            case 'year':
            case 'hour':
            case 'minute':
            case 'second':
                return type;
            case 'dayPeriod':
                if (labelFormat)
                    return '';
                return type;
            case 'literal':
                return value;
            default:
                return '';
        }
    });
};
export const getStartOfWeek = (date, calendarType = CALENDAR_TYPES.ISO_8601, timeZone) => {
    const startDate = new Date(date);
    const day = getDayOfWeek(startDate, calendarType, timeZone);
    if (day !== 0)
        startDate.setHours(startDate.getHours() - 24 * day);
    return startDate;
};
export const getEndOfWeek = (date, calendarType = CALENDAR_TYPES.ISO_8601, timeZone) => {
    const endDate = new Date(date);
    const day = getDayOfWeek(endDate, calendarType, timeZone);
    if (day !== 6)
        endDate.setHours(endDate.getHours() + 24 * (6 - day));
    return endDate;
};
export const convertWeekYearToDate = ({ week, year }) => {
    if ([week, year].every(Boolean)) {
        const dateOfWeek = getDateOfISOWeek(Number(week), Number(year));
        return {
            year: dateOfWeek.getUTCFullYear().toString(),
            month: dateOfWeek.getUTCMonth().toString(),
            day: dateOfWeek.getUTCDate().toString()
        };
    }
    return undefined;
};
export const is12HClockFormat = (localeTag) => {
    const meridiem = getIntlDateTimeFormatFromCache(localeTag, {
        hour: 'numeric'
    })
        .formatToParts()
        .find(({ type }) => type === 'dayPeriod');
    return !!meridiem?.value;
};
const objDate = new Date();
objDate.setUTCDate(1);
export const getLocalizedMonths = (localeTag) => {
    return [...Array(12).keys()].map(monthNo => {
        objDate.setUTCMonth(monthNo);
        return cap(objDate.toLocaleString(localeTag, { month: 'long', timeZone: 'UTC' }));
    });
};
export const getLocalizedYear = (year, localeTag) => {
    objDate.setUTCFullYear(year);
    return objDate.toLocaleString(localeTag, { year: 'numeric', timeZone: 'UTC' });
};
export const getLocalizedWeekDays = (localeTag) => {
    const calendarType = getCalendarTypeFromLocale(localeTag);
    return getWeekdays(calendarType).reduce((accumulator, dayDate, index) => {
        accumulator[index + 1] = cap(dayDate.toLocaleString(localeTag, {
            weekday: 'long'
        }));
        return accumulator;
    }, {});
};
export const MS_IN_SECOND = 1000;
export const MS_IN_MINUTE = 1000 * 60;
export const MS_IN_HOUR = 1000 * 60 * 60;
export const MS_IN_DAY = 1000 * 60 * 60 * 24;
export const msToTimeFormat = (ms) => {
    if (ms < 0) {
        return { hour: undefined, minute: undefined, second: undefined };
    }
    return {
        hour: Math.floor(ms / MS_IN_HOUR),
        minute: Math.floor((ms / MS_IN_MINUTE) % 60),
        second: Math.floor((ms / MS_IN_SECOND) % 60)
    };
};
export const timeFormatToMs = ({ hour, minute, second }) => {
    if (hour < 0 || minute < 0 || second < 0) {
        return 0;
    }
    return second * MS_IN_SECOND + minute * MS_IN_MINUTE + hour * MS_IN_HOUR;
};
export const msToTimeUnit = (ms, format) => {
    if (ms < 0) {
        return 0;
    }
    switch (format) {
        case 'seconds':
            return Math.floor(ms / MS_IN_SECOND);
        case 'minutes':
            return Math.floor(ms / MS_IN_MINUTE);
        case 'hours':
            return Math.floor(ms / MS_IN_HOUR);
        case 'days':
            return Math.floor(ms / MS_IN_DAY);
        default:
            return 0;
    }
};
export const timeUnitToMs = (count, format) => {
    if (count < 0) {
        return 0;
    }
    switch (format) {
        case 'seconds':
            return count * MS_IN_SECOND;
        case 'minutes':
            return count * MS_IN_MINUTE;
        case 'hours':
            return count * MS_IN_HOUR;
        case 'days':
            return count * MS_IN_DAY;
        default:
            return 0;
    }
};
export const getTimeOptions = (withSeconds, is12h = false) => {
    return {
        hour: 'numeric',
        minute: 'numeric',
        ...(withSeconds && { second: 'numeric' }),
        hour12: is12h
    };
};
export const validateDate = (year, month, day) => {
    return !(year.toString().length < 4 ||
        month < 1 ||
        month > 12 ||
        day < 1 ||
        day > new Date(year, month, 0).getDate());
};
export const validateTime = (hour, minute, second, millisecond, is12h) => {
    const hourMax = is12h ? 12 : 23;
    const hourMin = is12h ? 1 : 0;
    return !(hour > hourMax || hour < hourMin || minute > 59 || second > 59 || millisecond > 999);
};
export const validateDateTime = (year, month, day, hour, minute, second, millisecond, is12h) => {
    return validateDate(year, month, day) && validateTime(hour, minute, second, millisecond, is12h);
};
export const toUTCDateString = (date, format) => {
    const adjustedDate = new Date(date.getTime() + date.getTimezoneOffset() * 60000);
    return format?.format(adjustedDate) ?? adjustedDate.toDateString();
};
const ISO_DATE_REG_EXP = /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d(?::[0-5]\d)?(?:\.\d{3})?(?:[+-][0-2]\d:[0-5]\d|Z))/;
export const isISODate = (value) => {
    return ISO_DATE_REG_EXP.test(value);
};
const TIME_REG_EXP = /^([01]?\d|2[0-3]):[0-5]?\d(:[0-5]?\d)?(\.\d{1,3})?$/;
export const isTime = (value) => {
    return TIME_REG_EXP.test(value);
};
const DATE_PART_TOKEN_MAP = {
    year: 'YYYY',
    month: 'M',
    day: 'D'
};
const buildDayjsDateFormat = (localeTag) => {
    return getDateTimeParts(localeTag, {
        year: 'numeric',
        month: 'numeric',
        day: 'numeric'
    })
        .map(part => DATE_PART_TOKEN_MAP[part] ?? part)
        .join('');
};
const buildDayjsDateTimeFormat = (dateFormat, value, numParts) => {
    const hasMeridiem = /[AaPp][Mm]/.test(value);
    const hourToken = hasMeridiem ? 'h' : 'H';
    const meridiemSuffix = hasMeridiem ? 'A' : '';
    const secondsToken = numParts === 6 ? ':s' : '';
    return `${dateFormat} ${hourToken}:m${secondsToken}${meridiemSuffix}`;
};
const tryParseDateWithDayjs = (value, format) => {
    // Normalize meridiem to uppercase since dayjs A token only matches AM/PM
    const normalizedValue = value.replace(/[AaPp][Mm]/g, match => match.toUpperCase());
    const parsed = dayjs(normalizedValue, format);
    if (!parsed.isValid())
        return undefined;
    // Verify no overflow wrapping by comparing digit groups round-trip
    const normalizeDigits = (s) => s.match(/\d+/g)?.map(Number).join(',');
    if (normalizeDigits(parsed.format(format)) !== normalizeDigits(normalizedValue))
        return undefined;
    return parsed;
};
export const parsePastedTextToDate = (value, localeTag, currentDate, type = 'datetime') => {
    if (type === 'datetime' && isTime(value)) {
        const [hour, minute, second = 0, millisecond = 0] = value.split(':').map(Number);
        const result = new Date(currentDate.getTime());
        result.setUTCHours(hour, minute, second, millisecond);
        return result;
    }
    if (isISODate(value)) {
        return new Date(value);
    }
    const valueParts = value.match(/\d+/g);
    const validPartCounts = type === 'date' ? [3] : [3, 5, 6];
    if (!valueParts?.length || !validPartCounts.includes(valueParts.length))
        return undefined;
    const dateFormat = buildDayjsDateFormat(localeTag);
    const format = valueParts.length === 3
        ? dateFormat
        : buildDayjsDateTimeFormat(dateFormat, value, valueParts.length);
    const parsed = tryParseDateWithDayjs(value, format);
    if (!parsed)
        return undefined;
    const result = new Date(currentDate.getTime());
    result.setUTCFullYear(parsed.year(), parsed.month(), parsed.date());
    if (valueParts.length > 3) {
        result.setUTCHours(parsed.hour(), parsed.minute(), parsed.second(), 0);
    }
    return result;
};
export const getSelectedDateInfoText = (date, type, locale, translateFn, is12h) => {
    if (type === 'week') {
        const displayNames = new Intl.DisplayNames(locale, { style: 'long', type: 'dateTimeField' });
        return date
            ? `${translateFn('selected_date_a11y')} ${displayNames.of('weekOfYear')} ${getWeek(date, CALENDAR_TYPES.ISO_8601, 'UTC')}, ${getWeekYear(date, CALENDAR_TYPES.ISO_8601, 'UTC')}`
            : '';
    }
    return date
        ? `${translateFn('selected_date_a11y')} ${date.toLocaleString(locale, {
            timeZone: 'UTC',
            dateStyle: 'full',
            hour12: is12h,
            ...(type === 'datetime' ? { timeStyle: 'short' } : {})
        })}`
        : '';
};
export const generatePickerButtonAriaLabel = (date, type, locale, translateFn, open, is12h) => {
    const datePart = getSelectedDateInfoText(date, type, locale, translateFn, is12h);
    return `${datePart ? `${datePart}, ` : ''}${translateFn(open ? 'close_calendar_button_a11y' : 'open_calendar_button_a11y')}`;
};
export const getDateFormat = (t, localeTag, type) => {
    const formatDateParts = (parts) => {
        return parts
            .map(part => {
            switch (part) {
                case 'day':
                case 'month':
                case 'year':
                case 'hour':
                case 'minute':
                case 'second':
                    return t(`${part}_placeholder`);
                default:
                    return part;
            }
        })
            .join('');
    };
    switch (type) {
        case 'date':
            return formatDateParts(getDateTimeParts(localeTag));
        case 'time':
            return formatDateParts(getDateTimeParts(localeTag, getTimeOptions(false, false), true));
        case 'time+seconds':
            return formatDateParts(getDateTimeParts(localeTag, getTimeOptions(true, false), true));
        case 'datetime':
            return `${formatDateParts(getDateTimeParts(localeTag))} ${formatDateParts(getDateTimeParts(localeTag, getTimeOptions(false, false), true))}`;
        case 'datetime+seconds':
        default:
            return `${formatDateParts(getDateTimeParts(localeTag))} ${formatDateParts(getDateTimeParts(localeTag, getTimeOptions(true, false), true))}`;
    }
};
//# sourceMappingURL=utils.js.map