var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
import { VALID_UNITS } from './types';
/**
 * Find the correct field data in our CLDR data
 * @param locale locale
 */
function findFields(locale) {
    var localeData = RelativeTimeFormat.__localeData__;
    var data = localeData[locale.toLowerCase()];
    // The locale data is de-duplicated, so we have to traverse the locale's
    // hierarchy until we find `fields` to return.
    while (data) {
        if (data.fields) {
            return data.fields;
        }
        data = data.parentLocale
            ? localeData[data.parentLocale.toLowerCase()]
            : undefined;
    }
    throw new Error("Locale data added to RelativeTimeFormat is missing 'fields' for \"" + locale + "\"");
}
function resolveLocale(locales) {
    var localeData = RelativeTimeFormat.__localeData__, languageAliases = RelativeTimeFormat.__languageAliases__;
    var resolvedLocales = (Array.isArray(locales) ? locales : [locales]).concat([
        // default locale
        DEFAULT_LOCALE
    ]).filter(function (s) { return typeof s === 'string'; })
        .map(function (l) { return languageAliases[l] || l; });
    var i, len, localeParts, data;
    // Using the set of locales + the default locale, we look for the first one
    // which that has been registered. When data does not exist for a locale, we
    // traverse its ancestors to find something that's been registered within
    // its hierarchy of locales. Since we lack the proper `parentLocale` data
    // here, we must take a naive approach to traversal.
    for (i = 0, len = resolvedLocales.length; i < len; i += 1) {
        localeParts = resolvedLocales[i].toLowerCase().split('-');
        while (localeParts.length) {
            data = localeData[localeParts.join('-')];
            if (data) {
                // Return the normalized locale string; e.g., we return "en-US",
                // instead of "en-us".
                return data.locale;
            }
            localeParts.pop();
        }
    }
    var defaultLocale = resolvedLocales.pop();
    throw new Error('No locale data has been added to IntlRelativeTimeFormat for: ' +
        resolvedLocales.join(', ') +
        ', or the default locale: ' +
        defaultLocale);
}
function findFieldData(fields, unit, style) {
    if (style == 'long') {
        return fields[unit];
    }
    if (style == 'narrow') {
        return (fields[unit + "-narrow"] ||
            fields[unit + "-short"]);
    }
    return fields[unit + "-short"];
}
function objectIs(x, y) {
    if (Object.is) {
        return Object.is(x, y);
    }
    // SameValue algorithm
    if (x === y) {
        // Steps 1-5, 7-10
        // Steps 6.b-6.e: +0 != -0
        return x !== 0 || 1 / x === 1 / y;
    }
    // Step 6.a: NaN == NaN
    return x !== x && y !== y;
}
function resolvePastOrFuture(value) {
    return objectIs(value, -0)
        ? 'past'
        : objectIs(value, +0)
            ? 'future'
            : value < 0
                ? 'past'
                : 'future';
}
function validateInstance(instance, method) {
    if (!(instance instanceof RelativeTimeFormat)) {
        throw new TypeError("Method Intl.RelativeTimeFormat.prototype." + method + " called on incompatible receiver " + String(instance));
    }
}
function validateUnit(unit) {
    // `unit + ''` to guard against `Symbol()`
    if (!~VALID_UNITS.indexOf(unit + '')) {
        throw new RangeError("Invalid unit argument for format() '" + String(unit) + "'");
    }
    var resolvedUnit = (unit[unit.length - 1] === 's'
        ? unit.slice(0, unit.length - 1)
        : unit);
    return resolvedUnit;
}
function validateValue(value, method) {
    if (method === void 0) { method = 'format'; }
    var parsedValue = typeof value === 'string' ? new Number(value).valueOf() : value;
    if (!isFinite(parsedValue)) {
        throw new RangeError("Value need to be finite number for Intl.RelativeTimeFormat.prototype." + method + "()");
    }
    return parsedValue;
}
function isString(s) {
    return !!s;
}
function toObject(arg) {
    if (arg == null) {
        throw new TypeError('undefined/null cannot be converted to object');
    }
    return Object(arg);
}
/**
 * https://tc39.es/ecma402/#sec-getoption
 * @param opts
 * @param prop
 * @param type
 * @param values
 * @param fallback
 */
function getOption(opts, prop, type, values, fallback) {
    // const descriptor = Object.getOwnPropertyDescriptor(opts, prop);
    var value = opts[prop];
    if (value !== undefined) {
        if (type !== 'boolean' && type !== 'string') {
            throw new TypeError('invalid type');
        }
        if (type === 'boolean') {
            value = new Boolean(value);
        }
        if (type === 'string') {
            value = new String(value);
        }
        if (values !== undefined && !values.filter(function (val) { return val == value; }).length) {
            throw new RangeError(value + " in not within " + values);
        }
        return value;
    }
    return fallback;
}
function intersection(arr1, arr2) {
    return arr1.filter(function (s) { return ~arr2.indexOf(s); });
}
var DEFAULT_LOCALE = new Intl.NumberFormat().resolvedOptions().locale;
var RelativeTimeFormat = /** @class */ (function () {
    function RelativeTimeFormat() {
        var _a = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            _a[_i] = arguments[_i];
        }
        var locales = _a[0], options = _a[1];
        // test262/test/intl402/RelativeTimeFormat/constructor/constructor/newtarget-undefined.js
        // Cannot use `new.target` bc of IE11 & TS transpiles it to something else
        var newTarget = this && this instanceof RelativeTimeFormat ? this.constructor : void 0;
        if (!newTarget) {
            throw new TypeError("Intl.RelativeTimeFormat must be called with 'new'");
        }
        var opts = options === undefined ? Object.create(null) : toObject(options);
        if (locales === undefined) {
            this._locale = DEFAULT_LOCALE;
        }
        else {
            this._locale = resolveLocale(intersection(Intl.NumberFormat.supportedLocalesOf(locales), Intl.PluralRules.supportedLocalesOf(locales)));
        }
        this._localeMatcher = getOption(opts, 'localeMatcher', 'string', ['best fit', 'lookup'], 'best fit');
        this._style = getOption(opts, 'style', 'string', ['long', 'narrow', 'short'], 'long');
        this._numeric = getOption(opts, 'numeric', 'string', ['always', 'auto'], 'always');
        this._fields = findFields(this._locale);
        this._nf = new Intl.NumberFormat(this._locale);
        this._pl = new Intl.PluralRules(this._locale);
        this._numberingSystem = this._nf.resolvedOptions().numberingSystem;
    }
    RelativeTimeFormat.prototype.format = function (value, unit) {
        validateInstance(this, 'format');
        var resolvedUnit = validateUnit(unit);
        var parsedValue = validateValue(value);
        var _a = this, style = _a._style, numeric = _a._numeric;
        var fieldData = findFieldData(this._fields, resolvedUnit, style);
        if (!fieldData) {
            throw new Error("Unsupported unit " + unit);
        }
        var relative = fieldData.relative, relativeTime = fieldData.relativeTime;
        var result = '';
        // We got a match for things like yesterday
        if (numeric == 'auto' &&
            (result = relative[String(parsedValue)] || '')) {
            return result;
        }
        var selector = this._pl.select(parsedValue);
        var futureOrPastData = relativeTime[resolvePastOrFuture(parsedValue)];
        var msg = futureOrPastData[selector] || futureOrPastData.other;
        return msg.replace(/\{0\}/, this._nf.format(Math.abs(parsedValue)));
    };
    RelativeTimeFormat.prototype.formatToParts = function (value, unit) {
        validateInstance(this, 'format');
        var resolvedUnit = validateUnit(unit);
        var parsedValue = validateValue(value, 'formatToParts');
        var _a = this, style = _a._style, numeric = _a._numeric;
        var fieldData = findFieldData(this._fields, resolvedUnit, style);
        if (!fieldData) {
            throw new Error("Unsupported unit " + unit);
        }
        var relative = fieldData.relative, relativeTime = fieldData.relativeTime;
        var result = '';
        // We got a match for things like yesterday
        if (numeric == 'auto' &&
            (result = relative[String(parsedValue)] || '')) {
            return [
                {
                    type: 'literal',
                    value: result
                }
            ];
        }
        var selector = this._pl.select(parsedValue);
        var futureOrPastData = relativeTime[resolvePastOrFuture(parsedValue)];
        var msg = futureOrPastData[selector] || futureOrPastData.other;
        var valueParts = this._nf
            .formatToParts(Math.abs(parsedValue))
            .map(function (p) { return (__assign({}, p, { unit: resolvedUnit })); });
        return msg
            .split(/(\{0\})/)
            .filter(isString)
            .reduce(function (parts, str) { return parts.concat((str === '{0}'
            ? valueParts
            : [{ type: 'literal', value: str }])); }, []);
    };
    RelativeTimeFormat.prototype.resolvedOptions = function () {
        validateInstance(this, 'resolvedOptions');
        // test262/test/intl402/RelativeTimeFormat/prototype/resolvedOptions/type.js
        var opts = Object.create(Object.prototype);
        Object.defineProperties(opts, {
            locale: {
                value: this._locale,
                writable: true,
                enumerable: true,
                configurable: true
            },
            style: {
                value: this._style.valueOf(),
                writable: true,
                enumerable: true,
                configurable: true
            },
            numeric: {
                value: this._numeric.valueOf(),
                writable: true,
                enumerable: true,
                configurable: true
            },
            numberingSystem: {
                value: this._numberingSystem.valueOf(),
                writable: true,
                enumerable: true,
                configurable: true
            }
        });
        return opts;
    };
    RelativeTimeFormat.prototype.toString = function () {
        return '[object Intl.RelativeTimeFormat]';
    };
    RelativeTimeFormat.__addLocaleData = function () {
        var data = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            data[_i] = arguments[_i];
        }
        for (var _a = 0, data_1 = data; _a < data_1.length; _a++) {
            var datum = data_1[_a];
            if (!(datum && datum.locale)) {
                throw new Error('Locale data provided to RelativeTimeFormat is missing a ' +
                    '`locale` property value');
            }
            RelativeTimeFormat.__localeData__[datum.locale.toLowerCase()] = datum;
        }
    };
    RelativeTimeFormat.__setLanguageAliases = function (aliases) {
        RelativeTimeFormat.__languageAliases__ = aliases;
    };
    RelativeTimeFormat.supportedLocalesOf = function (locales) {
        var _a = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            _a[_i - 1] = arguments[_i];
        }
        var opts = _a[0];
        // test262/test/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-toobject.js
        var localeMatcher = 'best fit';
        // test262/test/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-null.js
        if (opts === null) {
            throw new TypeError('opts cannot be null');
        }
        if (opts) {
            localeMatcher = getOption(opts, 'localeMatcher', 'string', ['best fit', 'lookup'], 'best fit');
        }
        // test262/test/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/result-type.js
        return intersection(Intl.NumberFormat.supportedLocalesOf(locales, { localeMatcher: localeMatcher }), Intl.PluralRules.supportedLocalesOf(locales, { localeMatcher: localeMatcher })).slice();
    };
    RelativeTimeFormat.__localeData__ = {};
    RelativeTimeFormat.__languageAliases__ = {};
    RelativeTimeFormat.polyfilled = true;
    return RelativeTimeFormat;
}());
export default RelativeTimeFormat;
