"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ibanRegExp = exports.ssinRegExp = exports.nihiiRegExp = void 0;
exports.isValidIBAN = isValidIBAN;
exports.ibanValidate = ibanValidate;
exports.ibanFormat = ibanFormat;
exports.nihiiFormat = nihiiFormat;
exports.nihiiValidate = nihiiValidate;
exports.ssinFormat = ssinFormat;
exports.ssinValidate = ssinValidate;
exports.phoneNumberFormat = phoneNumberFormat;
exports.dateDecode = dateDecode;
exports.timeDecode = timeDecode;
exports.dateEncode = dateEncode;
exports.timeEncode = timeEncode;
exports.unit = unit;
exports.amount = amount;
exports.money = money;
exports.toUrlParams = toUrlParams;
exports.personName = personName;
exports.personNameAbbrev = personNameAbbrev;
exports.momentFormatToDateFnsFormat = momentFormatToDateFnsFormat;
exports.toMoment = toMoment;
const libphonenumber_js_1 = require("libphonenumber-js");
const date_fns_1 = require("date-fns");
// TODO: move this to env.js?
const DEFAULT_COUNTRY = 'BE';
exports.nihiiRegExp = new RegExp('^(\\d{1})(\\d{5})(\\d{2})(\\d{3})$');
exports.ssinRegExp = new RegExp('^(\\d{2})(\\d{2})(\\d{2})(\\d{3})(\\d{2})$');
exports.ibanRegExp = new RegExp('^(\\d{4})(\\d{4})(\\d{4})(\\d{4})$');
const patterns = {
    IBAN: (iban) => /^BE\d{14}$/.test(iban) && isValidIBAN(iban),
    IBANBE: (iban) => /^BE\d{14}$/.test(iban) && isValidIBAN(iban),
};
//http://ht5ifv.serprest.pt/extensions/tools/IBAN/
function isValidIBAN(iban) {
    if (iban === null || iban === undefined)
        return undefined;
    //This function check if the checksum if correct
    iban = iban.replace(/^(.{4})(.*)$/, '$2$1'); //Move the first 4 chars from left to the right
    const fun = (e) => (e.charCodeAt(0) - 'A'.charCodeAt(0) + 10).toString();
    iban = iban.replace(/[A-Z]/g, fun); //Convert A-Z to 10-25
    let $sum = 0;
    let $ei = 1; //First exponent
    for (let $i = iban.length - 1; $i >= 0; $i--) {
        $sum += $ei * parseInt(iban.charAt($i), 10); //multiply the digit by it's exponent
        $ei = ($ei * 10) % 97; //compute next base 10 exponent  in modulus 97
    }
    return $sum % 97 === 1;
}
function ibanValidate(iban) {
    if (iban === null || iban === undefined)
        return undefined;
    if (iban.startsWith('BE')) {
        return patterns.IBANBE(iban);
    }
    else {
        return patterns.IBAN(iban);
    }
}
function ibanFormat(iban) {
    if (iban === null || iban === undefined)
        return undefined;
    return iban.replace(exports.ibanRegExp, '$1 $2 $3 $4');
}
function nihiiFormat(nihii) {
    if (nihii === null || nihii === undefined)
        return undefined;
    return nihii.replace(exports.nihiiRegExp, '$1 $2 $3 $4');
}
function nihiiValidate(nihii) {
    if (nihii === null || nihii === undefined)
        return undefined;
    return !!nihii.match(exports.nihiiRegExp);
}
function ssinFormat(ssin) {
    if (ssin === null || ssin === undefined)
        return undefined;
    return ssin.replace(exports.ssinRegExp, '$1 $2 $3 $4 $5');
}
function ssinValidate(ssin) {
    if (ssin === null || ssin === undefined)
        return undefined;
    return !!ssin.match(exports.ssinRegExp);
}
/* Alternate lib free version
export function phoneNumberValidate(phoneNumber: string): boolean {
  return (
    !!phoneNumber.match(/(?:\+|00)([1-9][0-9]{1-2})([- /.]*([0-9]+))+/) ||
    !!phoneNumber.match(/(0[1-9][0-9]*)([- /.]*([0-9]+))+/)
  )
}

export function phoneNumberFormat(phoneNumber: string): string {
  let match = phoneNumber.match(/(?:\+|00)([1-9][0-9]{1-2})([- /.]*([0-9]+))+/)
  if (match) {
    return `+${match[1]} ${match[2].replace(/[- /.]/g, " ")}`.replace(/  /g, " ")
  }
  match = phoneNumber.match(/0([1-9][0-9]*)([- /.]*([0-9]+))+/)
  if (match) {
    return `+32 ${match[1]} ${match[2].replace(/[- /.]/g, " ")}`.replace(/  /g, " ")
  }
  return phoneNumber
}
*/
function phoneNumberFormat(phoneNumber) {
    if (phoneNumber === null || phoneNumber === undefined)
        return undefined;
    const parsedPhoneNumber = (0, libphonenumber_js_1.parseNumber)(phoneNumber, DEFAULT_COUNTRY);
    if (Object.keys(parsedPhoneNumber).length === 0) {
        // The number is not valid, so we leave the input string as-is.
        return phoneNumber;
    }
    return (0, libphonenumber_js_1.formatNumber)(parsedPhoneNumber, 'INTERNATIONAL');
}
/**
 * Converts a backend date number (e.g., patient birth date) into a Date object.
 * @param dateNumber a YYYYMMDD date number from the backend
 * @return a Date object
 * @throws Error if it is impossible to create a date from the number, other if dateNumber is negative.
 * @see #dateEncode
 * @see #timeDecode
 */
function dateDecode(dateNumber) {
    if (dateNumber === null || dateNumber === undefined)
        return undefined;
    if (dateNumber < 0) {
        throw new Error("We don't decode negative dates. Please make sure you have valid data.");
    }
    const dateNumberStr = dateNumber.toString().padStart(8, '19700101');
    if (dateNumberStr.length > 8) {
        if (dateNumberStr.endsWith('000000')) {
            return dateNumber ? (0, date_fns_1.parse)(dateNumberStr.slice(0, 8), 'yyyyMMdd', new Date()) : undefined;
        }
        throw Error("Decoded date is over year 9999. We can't format it properly.");
    }
    return dateNumber ? (0, date_fns_1.parse)(dateNumberStr, 'yyyyMMdd', new Date()) : undefined;
}
/**
 * Converts a backend time number (e.g., health element openingDate) into a Date object.
 * @param timeNumber a YYYYMMDD date number from the backend
 * @return a Date object
 * @see #timeEncode
 * @see #dateDecode
 */
function timeDecode(timeNumber) {
    if (timeNumber === null || timeNumber === undefined)
        return undefined;
    return timeNumber ? (0, date_fns_1.parse)(timeNumber.toString(), 'yyyyMMddHHmmss', new Date()) : undefined;
}
/**
 * Encodes a Date object into a backend date number (e.g., patient birth date).
 *
 * A Date is formatted as usual. A String or a Number is interpreted as a fuzzy date that is already
 * encoded (yyyyMMdd or yyyyMMddHHmmss) or as an epoch expressed in milliseconds.
 * @param date a Date object, or a fuzzy date / epoch as a String or Number
 * @return a YYYYMMDD date number for the backend
 * @see #dateDecode
 * @see #timeEncode
 */
function dateEncode(date) {
    if (date === null || date === undefined) {
        return undefined;
    }
    if (typeof date === 'string' && date.trim() === '')
        return undefined;
    if (date instanceof Date) {
        // date is null if the field is not set
        return date ? Number((0, date_fns_1.format)(date, 'yyyyMMdd').padStart(8, '19700101')) : undefined;
    }
    // A String or a Number is a fuzzy date (yyyyMMdd, yyyyMMddHHmmss) or an epoch in milliseconds
    const fuzzy = Number(date);
    if (!fuzzy || isNaN(fuzzy))
        return undefined;
    if (fuzzy >= 18000101 && fuzzy < 25400000) {
        // already a yyyyMMdd fuzzy date
        return fuzzy;
    }
    if (fuzzy >= 18000101000000) {
        // a yyyyMMddHHmmss fuzzy date: keep only the date part
        return Math.floor(fuzzy / 1000000);
    }
    // an epoch expressed in milliseconds
    return Number((0, date_fns_1.format)(new Date(fuzzy), 'yyyyMMdd'));
}
/**
 * Encodes a Date object into a backend time number (e.g., health element openingDate).
 *
 * A Date is formatted as usual. A String or a Number is interpreted as a fuzzy date that is already
 * encoded (yyyyMMdd or yyyyMMddHHmmss) or as an epoch expressed in milliseconds.
 * @param date a Date object, or a fuzzy date / epoch as a String or Number
 * @return a YYYYMMDDHHmmss date number for the backend
 * @see #timeDecode
 * @see #dateEncode
 */
function timeEncode(date) {
    if (date === null || date === undefined)
        return undefined;
    if (typeof date === 'string' && date.trim() === '')
        return undefined;
    if (date instanceof Date) {
        return date ? Number((0, date_fns_1.format)(date, 'yyyyMMddHHmmss')) : undefined;
    }
    // A String or a Number is a fuzzy date (yyyyMMdd, yyyyMMddHHmmss) or an epoch in milliseconds
    const fuzzy = Number(date);
    if (!fuzzy || isNaN(fuzzy))
        return undefined;
    if (fuzzy >= 18000101 && fuzzy < 25400000) {
        // a yyyyMMdd fuzzy date: pad the time part with zeroes
        return fuzzy * 1000000;
    }
    if (fuzzy >= 18000101000000) {
        // already a yyyyMMddHHmmss fuzzy date
        return fuzzy;
    }
    // an epoch expressed in milliseconds
    return Number((0, date_fns_1.format)(new Date(fuzzy), 'yyyyMMddHHmmss'));
}
/**
 * Formats a value and a physical unit into text.
 * @param value the numerical or string value to encode
 * @param unit the unit represented as a string (an empty string is also supported)
 */
function unit(value, unit) {
    if (value === null || value === undefined)
        return undefined;
    unit = unit || '';
    let separator;
    if (!unit || unit.startsWith('°')) {
        separator = '';
    }
    else {
        // including '%'
        separator = '\xa0';
    }
    return value + separator + unit;
}
/**
 * 0.1 + 0.2 = 0.30000000000000004. Use this function to be better at maths.
 * @param value number
 * @return the rounded number, two after the comma
 */
function amount(value) {
    if (value === null || value === undefined)
        return undefined;
    return Number(value.toFixed(2));
}
/**
 * A simple formatter to keep the logic across the app.
 * Input: 2.1 ; Output: 2.10€
 */
function money(value) {
    if (value === null || value === undefined)
        return undefined;
    return [value.toFixed(2), '€'].join('');
}
/**
 * Transform a dictionary to a url params.
 * From { key1: value1, key2: value2, ... } returns key1=value1&key2=value2&...=...
 */
function toUrlParams(params) {
    if (params === null || params === undefined)
        return undefined;
    return Object.entries(params)
        .map(([key, value]) => (value ? key + '=' + value : undefined))
        .filter(Boolean)
        .join('&');
}
function personName(person) {
    if (person === null || person === undefined)
        return undefined;
    return `${person.firstName || ''} ${person.lastName || ''}`.trim();
}
function personNameAbbrev(person) {
    if (person === null || person === undefined)
        return undefined;
    const firstName = person.firstName ? person.firstName[0] + '.' : undefined;
    return personName(Object.assign(Object.assign({}, person), { firstName }));
}
// Moment.js → date-fns format token mapping. Listed longest-first so the
// matcher consumes the longest valid moment token at each position.
const MOMENT_TO_DATE_FNS_TOKENS = [
    // Year
    ['YYYY', 'yyyy'],
    ['YY', 'yy'],
    ['Y', 'y'],
    // Month
    ['MMMM', 'MMMM'],
    ['MMM', 'MMM'],
    ['Mo', 'Mo'],
    ['MM', 'MM'],
    ['M', 'M'],
    // Quarter
    ['Qo', 'Qo'],
    ['Q', 'Q'],
    // Day of year (moment DDD/DDDD/DDDo → date-fns D/DDD/Do, additional tokens)
    ['DDDDo', 'Do'],
    ['DDDD', 'DDD'],
    ['DDDo', 'Do'],
    ['DDD', 'D'],
    // Day of month
    ['Do', 'do'],
    ['DD', 'dd'],
    ['D', 'd'],
    // Day of week
    ['dddd', 'EEEE'],
    ['ddd', 'EEE'],
    ['dd', 'EEEEEE'],
    ['do', 'eo'],
    ['d', 'e'],
    ['E', 'i'],
    // Week of year
    ['ww', 'ww'],
    ['wo', 'wo'],
    ['w', 'w'],
    ['WW', 'II'],
    ['Wo', 'Io'],
    ['W', 'I'],
    // Hour
    ['HH', 'HH'],
    ['H', 'H'],
    ['hh', 'hh'],
    ['h', 'h'],
    ['kk', 'kk'],
    ['k', 'k'],
    // AM/PM
    ['A', 'a'],
    ['a', 'aaa'],
    // Minute
    ['mm', 'mm'],
    ['m', 'm'],
    // Second
    ['ss', 'ss'],
    ['s', 's'],
    // Fractional seconds
    ['SSSS', 'SSSS'],
    ['SSS', 'SSS'],
    ['SS', 'SS'],
    ['S', 'S'],
    // Timezone offset
    ['ZZ', 'xx'],
    ['Z', 'xxx'],
    // Unix timestamp
    ['X', 't'],
    ['x', 'T'],
];
function momentFormatToDateFnsFormat(format) {
    if (format === null || format === undefined)
        return undefined;
    const parts = [];
    let literalBuffer = '';
    const flushLiteral = () => {
        if (literalBuffer.length > 0) {
            parts.push("'" + literalBuffer.replace(/'/g, "''") + "'");
            literalBuffer = '';
        }
    };
    let i = 0;
    while (i < format.length) {
        const c = format[i];
        // Moment bracketed literal: [text] → date-fns 'text'
        if (c === '[') {
            const end = format.indexOf(']', i);
            if (end === -1) {
                literalBuffer += format.substring(i + 1);
                i = format.length;
                continue;
            }
            literalBuffer += format.substring(i + 1, end);
            i = end + 1;
            continue;
        }
        // Match the longest moment token starting at the current position.
        let matched;
        for (const token of MOMENT_TO_DATE_FNS_TOKENS) {
            if (format.startsWith(token[0], i)) {
                matched = token;
                break;
            }
        }
        if (matched) {
            flushLiteral();
            parts.push(matched[1]);
            i += matched[0].length;
            continue;
        }
        // Non-token character: alphabetic chars must be escaped as literals,
        // otherwise date-fns would interpret them as its own tokens.
        if (/[A-Za-z]/.test(c)) {
            literalBuffer += c;
        }
        else {
            flushLiteral();
            parts.push(c);
        }
        i++;
    }
    flushLiteral();
    return parts.join('');
}
function toMoment(epochOrLongCalendar) {
    if (epochOrLongCalendar === null || epochOrLongCalendar === undefined)
        return undefined;
    if (!epochOrLongCalendar && epochOrLongCalendar !== 0) {
        return undefined;
    }
    const parsed = (epochOrLongCalendar >= 18000101 && epochOrLongCalendar < 25400000) ?
        (0, date_fns_1.parse)('' + epochOrLongCalendar, 'yyyyMMdd', new Date()) :
        (epochOrLongCalendar >= 18000101000000) ?
            (0, date_fns_1.parse)('' + epochOrLongCalendar, 'yyyyMMddHHmmss', new Date()) :
            new Date(epochOrLongCalendar);
    return {
        format: (format) => {
            if (format === null || format === undefined)
                return undefined;
            const dateFnsFormat = momentFormatToDateFnsFormat(format);
            if (dateFnsFormat === null || dateFnsFormat === undefined)
                return undefined;
            return (0, date_fns_1.format)(parsed, dateFnsFormat, {
                useAdditionalDayOfYearTokens: true,
                useAdditionalWeekYearTokens: true,
            });
        }
    };
}
//# sourceMappingURL=formatting-util.js.map