import { CanonicalizeLocaleList, CanonicalizeTimeZoneName, IsValidTimeZoneName, OrdinaryHasInstance, SupportedLocales, ToNumber, defineProperty, invariant } from "@formatjs/ecma402-abstract";
import Decimal from "decimal.js";
import { FormatDateTime } from "./abstract/FormatDateTime.js";
import { FormatDateTimeRange } from "./abstract/FormatDateTimeRange.js";
import { FormatDateTimeRangeToParts } from "./abstract/FormatDateTimeRangeToParts.js";
import { FormatDateTimeToParts } from "./abstract/FormatDateTimeToParts.js";
import { InitializeDateTimeFormat } from "./abstract/InitializeDateTimeFormat.js";
import { parseDateTimeSkeleton } from "./abstract/skeleton.js";
import { DATE_TIME_PROPS } from "./abstract/utils.js";
import links from "./data/links.generated.js";
import getInternalSlots from "./get_internal_slots.js";
import { unpack } from "./packer.js";
import "./types.js";
const UPPERCASED_LINKS = Object.keys(links).reduce((all, l) => {
	all[l.toUpperCase()] = links[l];
	return all;
}, {});
const RESOLVED_OPTIONS_KEYS = [
	"locale",
	"calendar",
	"numberingSystem",
	"dateStyle",
	"timeStyle",
	"timeZone",
	"hourCycle",
	"weekday",
	"era",
	"year",
	"month",
	"day",
	"hour",
	"minute",
	"second",
	"timeZoneName"
];
const formatDescriptor = {
	enumerable: false,
	configurable: true,
	get() {
		if (typeof this !== "object" || !OrdinaryHasInstance(DateTimeFormat, this)) {
			throw TypeError("Intl.DateTimeFormat format property accessor called on incompatible receiver");
		}
		const internalSlots = getInternalSlots(this);
		// eslint-disable-next-line @typescript-eslint/no-this-alias
		const dtf = this;
		let boundFormat = internalSlots.boundFormat;
		if (boundFormat === undefined) {
			// https://tc39.es/proposal-unified-intl-numberformat/section11/numberformat_diff_out.html#sec-number-format-functions
			boundFormat = (date) => {
				let x;
				if (date === undefined) {
					x = new Decimal(Date.now());
				} else {
					x = ToNumber(date);
				}
				return FormatDateTime(dtf, x, {
					getInternalSlots,
					localeData: DateTimeFormat.localeData,
					tzData: DateTimeFormat.tzData,
					getDefaultTimeZone: DateTimeFormat.getDefaultTimeZone
				});
			};
			try {
				// https://github.com/tc39/test262/blob/master/test/intl402/NumberFormat/prototype/format/format-function-name.js
				Object.defineProperty(boundFormat, "name", {
					configurable: true,
					enumerable: false,
					writable: false,
					value: ""
				});
			} catch {}
			internalSlots.boundFormat = boundFormat;
		}
		return boundFormat;
	}
};
try {
	// https://github.com/tc39/test262/blob/master/test/intl402/NumberFormat/prototype/format/name.js
	Object.defineProperty(formatDescriptor.get, "name", {
		configurable: true,
		enumerable: false,
		writable: false,
		value: "get format"
	});
} catch {}
export const DateTimeFormat = function(locales, options) {
	// Cannot use `new.target` bc of IE11 & TS transpiles it to something else
	if (!this || !OrdinaryHasInstance(DateTimeFormat, this)) {
		return new DateTimeFormat(locales, options);
	}
	InitializeDateTimeFormat(this, locales, options, {
		tzData: DateTimeFormat.tzData,
		uppercaseLinks: UPPERCASED_LINKS,
		availableLocales: DateTimeFormat.availableLocales,
		relevantExtensionKeys: DateTimeFormat.relevantExtensionKeys,
		getDefaultLocale: DateTimeFormat.getDefaultLocale,
		getDefaultTimeZone: DateTimeFormat.getDefaultTimeZone,
		getInternalSlots,
		localeData: DateTimeFormat.localeData
	});
	/** IMPL START */
	const internalSlots = getInternalSlots(this);
	const dataLocale = internalSlots.dataLocale;
	const dataLocaleData = DateTimeFormat.localeData[dataLocale];
	invariant(dataLocaleData !== undefined, `Cannot load locale-dependent data for ${dataLocale}.`);
	/** IMPL END */
};
// Static properties
defineProperty(DateTimeFormat, "supportedLocalesOf", { value: function supportedLocalesOf(locales, options) {
	return SupportedLocales(DateTimeFormat.availableLocales, CanonicalizeLocaleList(locales), options);
} });
defineProperty(DateTimeFormat.prototype, "resolvedOptions", { value: function resolvedOptions() {
	if (typeof this !== "object" || !OrdinaryHasInstance(DateTimeFormat, this)) {
		throw TypeError("Method Intl.DateTimeFormat.prototype.resolvedOptions called on incompatible receiver");
	}
	const internalSlots = getInternalSlots(this);
	const ro = {};
	for (const key of RESOLVED_OPTIONS_KEYS) {
		let value = internalSlots[key];
		if (key === "hourCycle") {
			const hour12 = value === "h11" || value === "h12" ? true : value === "h23" || value === "h24" ? false : undefined;
			if (hour12 !== undefined) {
				ro.hour12 = hour12;
			}
		}
		if (DATE_TIME_PROPS.indexOf(key) > -1) {
			if (internalSlots.dateStyle !== undefined || internalSlots.timeStyle !== undefined) {
				value = undefined;
			}
		}
		if (value !== undefined) {
			ro[key] = value;
		}
	}
	return ro;
} });
defineProperty(DateTimeFormat.prototype, "formatToParts", { value: function formatToParts(date) {
	let x;
	if (date === undefined) {
		x = new Decimal(Date.now());
	} else {
		x = ToNumber(date);
	}
	return FormatDateTimeToParts(this, x, {
		getInternalSlots,
		localeData: DateTimeFormat.localeData,
		tzData: DateTimeFormat.tzData,
		getDefaultTimeZone: DateTimeFormat.getDefaultTimeZone
	});
} });
defineProperty(DateTimeFormat.prototype, "formatRangeToParts", { value: function formatRangeToParts(startDate, endDate) {
	// oxlint-disable-next-line no-this-alias
	const dtf = this;
	invariant(typeof dtf === "object", "receiver is not an object", TypeError);
	invariant(startDate !== undefined && endDate !== undefined, "startDate/endDate cannot be undefined", TypeError);
	return FormatDateTimeRangeToParts(dtf, ToNumber(startDate), ToNumber(endDate), {
		getInternalSlots,
		localeData: DateTimeFormat.localeData,
		tzData: DateTimeFormat.tzData,
		getDefaultTimeZone: DateTimeFormat.getDefaultTimeZone
	});
} });
defineProperty(DateTimeFormat.prototype, "formatRange", { value: function formatRange(startDate, endDate) {
	// oxlint-disable-next-line no-this-alias
	const dtf = this;
	invariant(typeof dtf === "object", "receiver is not an object", TypeError);
	invariant(startDate !== undefined && endDate !== undefined, "startDate/endDate cannot be undefined", TypeError);
	return FormatDateTimeRange(dtf, ToNumber(startDate), ToNumber(endDate), {
		getInternalSlots,
		localeData: DateTimeFormat.localeData,
		tzData: DateTimeFormat.tzData,
		getDefaultTimeZone: DateTimeFormat.getDefaultTimeZone
	});
} });
const DEFAULT_TIMEZONE = "UTC";
DateTimeFormat.__setDefaultTimeZone = (timeZone) => {
	if (timeZone !== undefined) {
		timeZone = String(timeZone);
		if (!IsValidTimeZoneName(timeZone, {
			zoneNamesFromData: Object.keys(DateTimeFormat.tzData),
			uppercaseLinks: UPPERCASED_LINKS
		})) {
			throw new RangeError("Invalid timeZoneName");
		}
		timeZone = CanonicalizeTimeZoneName(timeZone, {
			zoneNames: Object.keys(DateTimeFormat.tzData),
			uppercaseLinks: UPPERCASED_LINKS
		});
	} else {
		timeZone = DEFAULT_TIMEZONE;
	}
	DateTimeFormat.__defaultTimeZone = timeZone;
};
DateTimeFormat.relevantExtensionKeys = [
	"nu",
	"ca",
	"hc"
];
DateTimeFormat.__defaultTimeZone = DEFAULT_TIMEZONE;
DateTimeFormat.getDefaultTimeZone = () => DateTimeFormat.__defaultTimeZone;
DateTimeFormat.__addLocaleData = function __addLocaleData(...data) {
	for (const { data: d, locale } of data) {
		const { dateFormat, timeFormat, dateTimeFormat, formats, intervalFormats, ...rawData } = d;
		const processedData = {
			...rawData,
			dateFormat: {
				full: parseDateTimeSkeleton(dateFormat.full),
				long: parseDateTimeSkeleton(dateFormat.long),
				medium: parseDateTimeSkeleton(dateFormat.medium),
				short: parseDateTimeSkeleton(dateFormat.short)
			},
			timeFormat: {
				full: parseDateTimeSkeleton(timeFormat.full),
				long: parseDateTimeSkeleton(timeFormat.long),
				medium: parseDateTimeSkeleton(timeFormat.medium),
				short: parseDateTimeSkeleton(timeFormat.short)
			},
			dateTimeFormat: {
				full: parseDateTimeSkeleton(dateTimeFormat.full).pattern,
				long: parseDateTimeSkeleton(dateTimeFormat.long).pattern,
				medium: parseDateTimeSkeleton(dateTimeFormat.medium).pattern,
				short: parseDateTimeSkeleton(dateTimeFormat.short).pattern
			},
			intervalFormatFallback: intervalFormats.intervalFormatFallback,
			formats: {}
		};
		for (const calendar in formats) {
			processedData.formats[calendar] = Object.keys(formats[calendar]).map((skeleton) => parseDateTimeSkeleton(skeleton, formats[calendar][skeleton], intervalFormats[skeleton], intervalFormats.intervalFormatFallback));
		}
		const minimizedLocale = new Intl.Locale(locale).minimize().toString();
		DateTimeFormat.localeData[locale] = DateTimeFormat.localeData[minimizedLocale] = processedData;
		DateTimeFormat.availableLocales.add(locale);
		DateTimeFormat.availableLocales.add(minimizedLocale);
		if (!DateTimeFormat.__defaultLocale) {
			DateTimeFormat.__defaultLocale = minimizedLocale;
		}
	}
};
Object.defineProperty(DateTimeFormat.prototype, "format", formatDescriptor);
DateTimeFormat.__defaultLocale = "";
DateTimeFormat.localeData = {};
DateTimeFormat.availableLocales = new Set();
DateTimeFormat.getDefaultLocale = () => {
	return DateTimeFormat.__defaultLocale;
};
DateTimeFormat.polyfilled = true;
DateTimeFormat.tzData = {};
DateTimeFormat.__addTZData = function(d) {
	DateTimeFormat.tzData = unpack(d);
};
try {
	if (typeof Symbol !== "undefined") {
		Object.defineProperty(DateTimeFormat.prototype, Symbol.toStringTag, {
			value: "Intl.DateTimeFormat",
			writable: false,
			enumerable: false,
			configurable: true
		});
	}
	Object.defineProperty(DateTimeFormat.prototype.constructor, "length", {
		value: 1,
		writable: false,
		enumerable: false,
		configurable: true
	});
} catch {}
