import { RangePatternType } from "@formatjs/ecma402-abstract";
/**
* https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
* Credit: https://github.com/caridy/intl-datetimeformat-pattern/blob/master/index.js
* with some tweaks
*/
const DATE_TIME_REGEX = /(?:[Eec]{1,6}|G{1,5}|[Qq]{1,5}|(?:[yYur]+|U{1,5})|[ML]{1,5}|d{1,2}|D{1,3}|F{1}|[abB]{1,5}|[hkHK]{1,2}|w{1,2}|W{1}|m{1,2}|s{1,2}|[zZOvVxX]{1,4})(?=([^']*'[^']*')*[^']*$)/g;
// trim patterns after transformations
const expPatternTrimmer = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
function matchSkeletonPattern(match, result) {
	const len = match.length;
	switch (match[0]) {
		case "G":
			result.era = len === 4 ? "long" : len === 5 ? "narrow" : "short";
			return "{era}";
		case "y":
		case "Y":
		case "u":
		case "U":
		case "r":
			result.year = len === 2 ? "2-digit" : "numeric";
			return "{year}";
		case "q":
		case "Q": throw new RangeError("`w/Q` (quarter) patterns are not supported");
		case "M":
		case "L":
			result.month = [
				"numeric",
				"2-digit",
				"short",
				"long",
				"narrow"
			][len - 1];
			return "{month}";
		case "w":
		case "W": throw new RangeError("`w/W` (week of year) patterns are not supported");
		case "d":
			result.day = ["numeric", "2-digit"][len - 1];
			return "{day}";
		case "D":
		case "F":
		case "g":
			result.day = "numeric";
			return "{day}";
		case "E":
			result.weekday = len === 4 ? "long" : len === 5 ? "narrow" : "short";
			return "{weekday}";
		case "e":
			result.weekday = [
				undefined,
				undefined,
				"short",
				"long",
				"narrow",
				"short"
			][len - 1];
			return "{weekday}";
		case "c":
			result.weekday = [
				undefined,
				undefined,
				"short",
				"long",
				"narrow",
				"short"
			][len - 1];
			return "{weekday}";
		case "a":
		case "b":
		case "B":
			result.hour12 = true;
			return "{ampm}";
		case "h":
			result.hour = ["numeric", "2-digit"][len - 1];
			result.hour12 = true;
			return "{hour}";
		case "H":
			result.hour = ["numeric", "2-digit"][len - 1];
			return "{hour}";
		case "K":
			result.hour = ["numeric", "2-digit"][len - 1];
			result.hour12 = true;
			return "{hour}";
		case "k":
			result.hour = ["numeric", "2-digit"][len - 1];
			return "{hour}";
		case "j":
		case "J":
		case "C": throw new RangeError("`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead");
		case "m":
			result.minute = ["numeric", "2-digit"][len - 1];
			return "{minute}";
		case "s":
			result.second = ["numeric", "2-digit"][len - 1];
			return "{second}";
		case "S":
		case "A":
			result.second = "numeric";
			return "{second}";
		case "z":
		case "Z":
		case "O":
		case "v":
		case "V":
		case "X":
		case "x":
			result.timeZoneName = len < 4 ? "short" : "long";
			return "{timeZoneName}";
	}
	return "";
}
function skeletonTokenToTable2(c) {
	switch (c) {
		case "G": return "era";
		case "y":
		case "Y":
		case "u":
		case "U":
		case "r": return "year";
		case "M":
		case "L": return "month";
		case "d":
		case "D":
		case "F":
		case "g": return "day";
		case "a":
		case "b":
		case "B": return "ampm";
		case "h":
		case "H":
		case "K":
		case "k": return "hour";
		case "m": return "minute";
		case "s":
		case "S":
		case "A": return "second";
		default: throw new RangeError("Invalid range pattern token");
	}
}
export function processDateTimePattern(pattern, result) {
	const literals = [];
	// Use skeleton to populate result, but use mapped pattern to populate pattern
	let pattern12 = pattern.replace(/'{2}/g, "{apostrophe}").replace(/'(.*?)'/g, (_, literal) => {
		literals.push(literal);
		return `$$${literals.length - 1}$$`;
	}).replace(DATE_TIME_REGEX, (m) => matchSkeletonPattern(m, result || {}));
	//Restore literals
	if (literals.length) {
		pattern12 = pattern12.replace(/\$\$(\d+)\$\$/g, (_, i) => {
			return literals[+i];
		}).replace(/\{apostrophe\}/g, "'");
	}
	// Handle apostrophe-escaped things
	return [pattern12.replace(/([\s\uFEFF\xA0])\{ampm\}([\s\uFEFF\xA0])/, "$1").replace("{ampm}", "").replace(expPatternTrimmer, ""), pattern12];
}
/**
* Parse Date time skeleton into Intl.DateTimeFormatOptions
* Ref: https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
* @public
* @param skeleton skeleton string
*/
export function parseDateTimeSkeleton(skeleton, rawPattern = skeleton, rangePatterns, intervalFormatFallback) {
	const result = {
		pattern: "",
		pattern12: "",
		skeleton,
		rawPattern,
		rangePatterns: {},
		rangePatterns12: {}
	};
	if (rangePatterns) {
		for (const k in rangePatterns) {
			const key = skeletonTokenToTable2(k);
			const rawPattern = rangePatterns[k];
			const intervalResult = { patternParts: [] };
			const [pattern, pattern12] = processDateTimePattern(rawPattern, intervalResult);
			result.rangePatterns[key] = {
				...intervalResult,
				patternParts: splitRangePattern(pattern)
			};
			result.rangePatterns12[key] = {
				...intervalResult,
				patternParts: splitRangePattern(pattern12)
			};
		}
	}
	if (intervalFormatFallback) {
		const patternParts = splitFallbackRangePattern(intervalFormatFallback);
		result.rangePatterns.default = { patternParts };
		result.rangePatterns12.default = { patternParts };
	}
	// Process skeleton
	skeleton.replace(DATE_TIME_REGEX, (m) => matchSkeletonPattern(m, result));
	const [pattern, pattern12] = processDateTimePattern(rawPattern);
	result.pattern = pattern;
	result.pattern12 = pattern12;
	return result;
}
export function splitFallbackRangePattern(pattern) {
	const parts = pattern.split(/(\{[0|1]\})/g).filter(Boolean);
	return parts.map((pattern) => {
		switch (pattern) {
			case "{0}": return {
				source: RangePatternType.startRange,
				pattern
			};
			case "{1}": return {
				source: RangePatternType.endRange,
				pattern
			};
			default: return {
				source: RangePatternType.shared,
				pattern
			};
		}
	});
}
export function splitRangePattern(pattern) {
	const PART_REGEX = /\{(.*?)\}/g;
	// Map of part and index within the string
	const parts = {};
	let match;
	let splitIndex = 0;
	while (match = PART_REGEX.exec(pattern)) {
		if (!(match[0] in parts)) {
			parts[match[0]] = match.index;
		} else {
			splitIndex = match.index;
			break;
		}
	}
	if (!splitIndex) {
		return [{
			source: RangePatternType.startRange,
			pattern
		}];
	}
	return [{
		source: RangePatternType.startRange,
		pattern: pattern.slice(0, splitIndex)
	}, {
		source: RangePatternType.endRange,
		pattern: pattern.slice(splitIndex)
	}];
}
