const NEW_LINES_REGEXP = /[\n\r]/gm;
const parseInts = (a) => a.map((v) => parseInt(v, 10));
/**
 * HTML input element value sanitizer.
 */
export default class HTMLInputElementValueSanitizer {
    /**
     * Sanitizes a value.
     *
     * @param input Input.
     * @param value Value.
     */
    static sanitize(input, value) {
        switch (input.type) {
            case 'password':
            case 'search':
            case 'tel':
            case 'text':
                return value.replace(NEW_LINES_REGEXP, '');
            case 'color':
                // https://html.spec.whatwg.org/multipage/forms.html#color-state-(type=color):value-sanitization-algorithm
                return /^#[a-fA-F\d]{6}$/.test(value) ? value.toLowerCase() : '#000000';
            case 'email':
                // https://html.spec.whatwg.org/multipage/forms.html#e-mail-state-(type=email):value-sanitization-algorithm
                // https://html.spec.whatwg.org/multipage/forms.html#e-mail-state-(type=email):value-sanitization-algorithm-2
                if (input.multiple) {
                    return value
                        .split(',')
                        .map((token) => token.trim())
                        .join(',');
                }
                return value.trim().replace(NEW_LINES_REGEXP, '');
            case 'number':
                // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):value-sanitization-algorithm
                return !isNaN(Number.parseFloat(value)) ? value : '';
            case 'range': {
                // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):value-sanitization-algorithm
                const number = Number.parseFloat(value);
                const min = parseFloat(input.min) || 0;
                const max = parseFloat(input.max) || 100;
                if (isNaN(number)) {
                    return max < min ? String(min) : String((min + max) / 2);
                }
                else if (number < min) {
                    return String(min);
                }
                else if (number > max) {
                    return String(max);
                }
                return value;
            }
            case 'url':
                // https://html.spec.whatwg.org/multipage/forms.html#url-state-(type=url):value-sanitization-algorithm
                return value.trim().replace(NEW_LINES_REGEXP, '');
            case 'date':
                // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):value-sanitization-algorithm
                value = this.sanitizeDate(value);
                return value && this.checkBoundaries(value, input.min, input.max) ? value : '';
            case 'datetime-local': {
                // https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local):value-sanitization-algorithm
                const match = value.match(/^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d)(?::(\d\d)(?:\.(\d{1,3}))?)?$/);
                if (!match) {
                    return '';
                }
                const dateString = this.sanitizeDate(value.slice(0, 10));
                let timeString = this.sanitizeTime(value.slice(11));
                if (!(dateString && timeString)) {
                    return '';
                }
                // Has seconds so needs to remove trailing zeros
                if (match[6] !== undefined) {
                    if (timeString.indexOf('.') !== -1) {
                        // Remove unecessary zeros milliseconds
                        timeString = timeString.replace(/(?:\.0*|(\.\d+?)0+)$/, '$1');
                    }
                    timeString = timeString.replace(/(\d\d:\d\d)(:00)$/, '$1');
                }
                return dateString + 'T' + timeString;
            }
            case 'month':
                // https://html.spec.whatwg.org/multipage/input.html#month-state-(type=month):value-sanitization-algorithm
                if (!(value.match(/^(\d\d\d\d)-(\d\d)$/) && this.parseMonthComponent(value))) {
                    return '';
                }
                return this.checkBoundaries(value, input.min, input.max) ? value : '';
            case 'time': {
                // https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):value-sanitization-algorithm
                value = this.sanitizeTime(value);
                return value && this.checkBoundaries(value, input.min, input.max) ? value : '';
            }
            case 'week': {
                // https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week):value-sanitization-algorithm
                const match = value.match(/^(\d\d\d\d)-W(\d\d)$/);
                if (!match) {
                    return '';
                }
                const [intY, intW] = parseInts(match.slice(1, 3));
                if (intY <= 0 || intW < 1 || intW > 53) {
                    return '';
                }
                // Check date is valid
                const lastWeek = this.lastIsoWeekOfYear(intY);
                if (intW < 1 || intW > 52 + lastWeek) {
                    return '';
                }
                if (!this.checkBoundaries(value, input.min, input.max)) {
                    return '';
                }
                return value;
            }
        }
        return value;
    }
    /**
     * Checks if a value is within the boundaries of min and max.
     *
     * @param value
     * @param min
     * @param max
     */
    static checkBoundaries(value, min, max) {
        if (min && min > value) {
            return false;
        }
        else if (max && max < value) {
            return false;
        }
        return true;
    }
    /**
     * Parses the month component of a date string.
     *
     * @param value
     */
    static parseMonthComponent(value) {
        const [Y, M] = value.split('-');
        const [intY, intM] = parseInts([Y, M]);
        if (isNaN(intY) || isNaN(intM) || intY <= 0 || intM < 1 || intM > 12) {
            return '';
        }
        return value;
    }
    /**
     * Returns the last ISO week of a year.
     *
     * @param year
     */
    static lastIsoWeekOfYear = (year) => {
        const date = new Date(+year, 11, 31);
        const day = (date.getDay() + 6) % 7;
        date.setDate(date.getDate() - day + 3);
        const firstThursday = date.getTime();
        date.setMonth(0, 1);
        if (date.getDay() !== 4) {
            date.setMonth(0, 1 + ((4 - date.getDay() + 7) % 7));
        }
        return 1 + Math.ceil((firstThursday - date.getTime()) / 604800000);
    };
    /**
     * Sanitizes a date string.
     *
     * @param value
     */
    static sanitizeDate(value) {
        const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
        if (!match) {
            return '';
        }
        const month = this.parseMonthComponent(value.slice(0, 7));
        if (!month) {
            return '';
        }
        const [intY, intM, intD] = parseInts(match.slice(1, 4));
        if (intD < 1 || intD > 31) {
            return '';
        }
        // Check date is valid
        const lastDayOfMonth = new Date(intY, intM, 0).getDate();
        if (intD > lastDayOfMonth) {
            return '';
        }
        return value;
    }
    /**
     * Sanitizes a time string.
     *
     * @param value
     */
    static sanitizeTime(value) {
        const match = value.match(/^(\d{2}):(\d{2})(?::(\d{2}(?:\.(\d{1,3}))?))?$/);
        if (!match) {
            return '';
        }
        const [intH, intM] = parseInts(match.slice(1, 3));
        const ms = parseFloat(match[3] || '0') * 1000;
        if (intH > 23 || intM > 59 || ms > 59999) {
            return '';
        }
        if (ms === 0) {
            return `${match[1]}:${match[2]}`;
        }
        else {
            return `${match[1]}:${match[2]}${ms >= 10000 ? `:${ms / 1000}` : `:0${ms / 1000}`}`;
        }
    }
}
//# sourceMappingURL=HTMLInputElementValueSanitizer.js.map