import { cap } from '../../utils';
import { CALENDAR_TYPES } from './Input/local';
import { getEndOfWeek, getQuarterForDateTimeZone, getStartOfWeek, getWeek, getWeekYear } from './Input/utils';
const unitsInMs = {
    year: 24 * 60 * 60 * 1000 * 365,
    month: (24 * 60 * 60 * 1000 * 365) / 12,
    day: 24 * 60 * 60 * 1000,
    hour: 60 * 60 * 1000,
    minute: 60 * 1000,
    second: 1000
};
const dateShort = {
    dateStyle: 'short'
};
const dateMedium = {
    dateStyle: 'medium'
};
const dateLong = {
    dateStyle: 'long'
};
const dateNumeric = {
    year: 'numeric',
    month: 'numeric',
    day: 'numeric'
};
const timeShort = {
    timeStyle: 'short'
};
const timeMedium = {
    timeStyle: 'medium'
};
const timeLong = {
    timeStyle: 'medium'
};
const timeNumeric = {
    hour: 'numeric',
    minute: 'numeric'
};
const dateTimeShort = {
    ...dateShort,
    ...timeShort
};
const dateTimeMedium = {
    ...dateMedium,
    ...timeShort
};
const dateTimeLong = {
    ...dateLong,
    ...timeMedium
};
const dateTimeNumeric = {
    ...dateNumeric,
    ...timeNumeric
};
const formatMapping = {
    date: {
        short: dateShort,
        medium: dateMedium,
        long: dateLong,
        numeric: dateNumeric
    },
    datetime: {
        short: dateTimeShort,
        medium: dateTimeMedium,
        long: dateTimeLong,
        numeric: dateTimeNumeric
    },
    datetime12: {
        short: { ...dateTimeShort, hour12: true },
        medium: { ...dateTimeMedium, hour12: true },
        long: { ...dateTimeLong, hour12: true },
        numeric: { ...dateTimeNumeric, hour12: true }
    },
    datetime24: {
        short: { ...dateTimeShort, hour12: false },
        medium: { ...dateTimeMedium, hour12: false },
        long: { ...dateTimeLong, hour12: false },
        numeric: { ...dateTimeNumeric, hour12: false }
    },
    time: {
        short: timeShort,
        medium: timeMedium,
        long: timeLong,
        numeric: timeNumeric
    },
    time12: {
        short: { ...timeShort, hour12: true },
        medium: { ...timeMedium, hour12: true },
        long: { ...timeLong, hour12: true },
        numeric: { ...timeNumeric, hour12: true }
    },
    time24: {
        short: { ...timeShort, hour12: false },
        medium: { ...timeMedium, hour12: false },
        long: { ...timeLong, hour12: false },
        numeric: { ...timeNumeric, hour12: false }
    },
    month: {
        short: { month: 'short' },
        medium: { month: 'short' },
        long: { month: 'long' },
        numeric: { month: 'numeric' }
    },
    monthyear: {
        short: { year: 'numeric', month: 'short' },
        medium: { year: 'numeric', month: 'short' },
        long: { year: 'numeric', month: 'long' },
        numeric: { year: 'numeric', month: 'numeric' }
    },
    week: {
        short: { month: 'short', day: 'numeric' },
        medium: { month: 'short', day: 'numeric' },
        long: { month: 'long', day: 'numeric' },
        numeric: { month: 'numeric', day: 'numeric' }
    },
    year: {
        short: { year: 'numeric' },
        medium: { year: 'numeric' },
        long: { year: 'numeric' },
        numeric: { year: '2-digit' }
    },
    relative: {
        short: { style: 'long', numeric: 'auto' },
        medium: { style: 'long', numeric: 'auto' },
        long: { style: 'long', numeric: 'auto' },
        numeric: { style: 'short', numeric: 'auto' }
    }
};
const dateTimeFormatCache = new Map();
export function getIntlDateTimeFormatFromCache(locale, options = {}) {
    const sortedOptions = JSON.stringify(options, Object.keys(options).sort());
    const key = `${locale}${sortedOptions}`;
    if (!dateTimeFormatCache.has(key)) {
        dateTimeFormatCache.set(key, new Intl.DateTimeFormat(locale, options));
    }
    return dateTimeFormatCache.get(key);
}
const relativeTimeFormatCache = {};
const displayNamesCache = {};
export function formatDateTime(value, { t, locale, format, variant, clockFormat, calendarType = CALENDAR_TYPES.ISO_8601, timeZone }) {
    const getFormattedDate = (date, dateVariant) => {
        const resolvedVariant = dateVariant === 'time' || dateVariant === 'datetime'
            ? `${dateVariant}${clockFormat ?? ''}`
            : dateVariant;
        const resolvedOptions = formatMapping[resolvedVariant][format];
        return getIntlDateTimeFormatFromCache(locale, {
            ...resolvedOptions,
            timeZone
        }).format(date);
    };
    const relativeTimeFormatter = (relativeValue, unit) => {
        const resolvedOptions = formatMapping.relative[format];
        relativeTimeFormatCache[locale] ??= new Map();
        const map = relativeTimeFormatCache[locale];
        if (!map.has(resolvedOptions)) {
            map.set(resolvedOptions, new Intl.RelativeTimeFormat(locale, resolvedOptions));
        }
        return map.get(resolvedOptions).format(relativeValue, unit);
    };
    const getUnitsElapsed = (date, unit) => {
        let amount;
        const now = new Date();
        switch (unit) {
            case 'year':
                amount = date.getUTCFullYear() - now.getUTCFullYear();
                break;
            case 'month':
                if (date.getUTCFullYear() === now.getUTCFullYear()) {
                    amount = date.getUTCMonth() - now.getUTCMonth();
                }
                else if (date < now) {
                    amount = (11 - date.getUTCMonth() + now.getUTCMonth() + 1) * -1;
                }
                else {
                    amount = 11 - now.getUTCMonth() + date.getUTCMonth() + 1;
                }
                break;
            case 'day':
                if (date.getUTCMonth() === now.getUTCMonth()) {
                    amount = date.getUTCDate() - now.getUTCDate();
                }
                else if (date < now) {
                    const maximalDayOfMonth = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0)).getUTCDate();
                    amount = (maximalDayOfMonth - date.getUTCDate() + now.getUTCDate()) * -1;
                }
                else {
                    const maximalDayOfMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 0)).getUTCDate();
                    amount = maximalDayOfMonth - now.getUTCDate() + date.getUTCDate() + 1;
                }
                break;
            case 'hour':
                if (date.getUTCDate() === now.getUTCDate()) {
                    amount = date.getUTCHours() - now.getUTCHours();
                }
                else if (date < now) {
                    amount = (23 - date.getUTCHours() + now.getUTCHours() + 1) * -1;
                }
                else {
                    amount = 23 - now.getUTCHours() + date.getUTCHours() + 1;
                }
                break;
            case 'minute':
                if (date.getUTCHours() === now.getUTCHours()) {
                    amount = date.getUTCMinutes() - now.getUTCMinutes();
                }
                else if (date < now) {
                    amount = (59 - date.getUTCMinutes() + now.getUTCMinutes() + 1) * -1;
                }
                else {
                    amount = 59 - now.getUTCMinutes() + date.getUTCMinutes() + 1;
                }
                break;
            default:
                amount = 0;
                break;
        }
        return amount;
    };
    const getRelativeTime = (date) => {
        let elapsed = date.getTime() - Date.now();
        // Deliberately reduce precision to 1 minute
        if (Math.abs(elapsed) < 60000)
            elapsed = 0;
        const unit = (Object.keys(unitsInMs).find(u => Math.abs(elapsed) >= unitsInMs[u]) ||
            'second');
        const amount = getUnitsElapsed(date, unit);
        return relativeTimeFormatter(amount, unit);
    };
    const getFormattedWeek = (date) => {
        displayNamesCache[locale] ??= new Intl.DisplayNames(locale, {
            style: 'long',
            type: 'dateTimeField'
        });
        const displayNames = displayNamesCache[locale];
        const startDate = getFormattedDate(getStartOfWeek(date, calendarType, timeZone), 'week');
        const endDate = getFormattedDate(getEndOfWeek(date, calendarType, timeZone), 'week');
        return `${cap(displayNames.of('weekOfYear'))} ${getWeek(date, calendarType, timeZone)}, ${getWeekYear(date, calendarType, timeZone)} (${startDate} - ${endDate})`;
    };
    const getFormattedQuarterYear = (date) => {
        const formattedYear = getFormattedDate(date, 'year');
        const quarterNumber = getQuarterForDateTimeZone(date, timeZone);
        return `${t(`date_quarter_q${quarterNumber}`, [formattedYear])}`;
    };
    switch (variant) {
        case 'relative':
            return getRelativeTime(value);
        case 'week':
            return getFormattedWeek(value);
        case 'quarteryear':
            return getFormattedQuarterYear(value);
        default:
            return getFormattedDate(value, variant);
    }
}
const dayInMs = 24 * 60 * 60 * 1000;
const hourInMs = 60 * 60 * 1000;
const minuteInMs = 60 * 1000;
const secondInMs = 1000;
const numberFormatCache = {};
export function formatDuration(valueInMs, { locale, significantUnits, unitDisplay = 'narrow', maxPrecision = 'second' }) {
    numberFormatCache[locale] ??= {};
    const sign = valueInMs < 0 ? '-' : '';
    const absMs = Math.abs(valueInMs);
    const durationParts = [
        { unit: 'day', value: Math.floor(absMs / dayInMs) },
        { unit: 'hour', value: Math.floor((absMs % dayInMs) / hourInMs) },
        { unit: 'minute', value: Math.floor((absMs % hourInMs) / minuteInMs) },
        { unit: 'second', value: Math.floor((absMs % minuteInMs) / secondInMs) },
        { unit: 'millisecond', value: absMs % secondInMs }
    ];
    const result = [];
    for (let i = 0; result.length < significantUnits && i < durationParts.length; i += 1) {
        const { value, unit } = durationParts[i];
        if (value > 0 || (unit === maxPrecision && result.length === 0)) {
            numberFormatCache[locale][unit] ??= {};
            numberFormatCache[locale][unit][unitDisplay] ??= new Intl.NumberFormat(locale, {
                style: 'unit',
                unit,
                unitDisplay
            });
            result.push(numberFormatCache[locale][unit][unitDisplay].format(value));
        }
        if (durationParts[i].unit === maxPrecision)
            break;
    }
    return `${sign}${result.join(' ')}`;
}
/**
 * @returns Current date and time value adjusted for local time zone.
 */
export const getAdjustedUTCDateTime = () => {
    const now = new Date();
    now.setTime(now.getTime() - now.getTimezoneOffset() * 60000);
    return now;
};
//# sourceMappingURL=utils.js.map