"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatCores = exports.formatBytesAsGiB = exports.formatBytesAsMiB = exports.formatToFractionalDigits = exports.secondsToNanoSeconds = exports.convertToBaseValue = exports.validate = exports.units = exports.humanizePercentage = exports.humanizeCpuCores = exports.humanizeSeconds = exports.humanizeNumberSI = exports.humanizeNumber = exports.humanizePacketsPerSec = exports.humanizeDecimalBytesPerSec = exports.humanizeDecimalBytes = exports.humanizeBinaryBytes = exports.humanizeBinaryBytesWithoutB = exports.getType = void 0;
const tslib_1 = require("tslib");
const defaults_js_1 = tslib_1.__importDefault(require("lodash-es/defaults.js"));
const findIndex_js_1 = tslib_1.__importDefault(require("lodash-es/findIndex.js"));
const isPlainObject_js_1 = tslib_1.__importDefault(require("lodash-es/isPlainObject.js"));
const isString_js_1 = tslib_1.__importDefault(require("lodash-es/isString.js"));
const toNumber_js_1 = tslib_1.__importDefault(require("lodash-es/toNumber.js"));
const TYPES = {
    numeric: {
        units: ['', 'k', 'm', 'b'],
        space: false,
        divisor: 1000,
    },
    decimalBytes: {
        units: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'],
        space: true,
        divisor: 1000,
    },
    decimalBytesWithoutB: {
        units: ['', 'k', 'M', 'G', 'T', 'P', 'E'],
        space: true,
        divisor: 1000,
    },
    binaryBytes: {
        units: ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'],
        space: true,
        divisor: 1024,
    },
    binaryBytesWithoutB: {
        units: ['i', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei'],
        space: true,
        divisor: 1024,
    },
    SI: {
        units: ['', 'k', 'M', 'G', 'T', 'P', 'E'],
        space: false,
        divisor: 1000,
    },
    decimalBytesPerSec: {
        units: ['Bps', 'KBps', 'MBps', 'GBps', 'TBps', 'PBps', 'EBps'],
        space: true,
        divisor: 1000,
    },
    packetsPerSec: {
        units: ['pps', 'kpps'],
        space: true,
        divisor: 1000,
    },
    seconds: {
        units: ['ns', 'μs', 'ms', 's'],
        space: true,
        divisor: 1000,
    },
};
const getType = (name) => {
    const type = TYPES[name];
    if (!(0, isPlainObject_js_1.default)(type)) {
        return {
            units: [],
            space: false,
            divisor: 1000,
        };
    }
    return type;
};
exports.getType = getType;
const convertBaseValueToUnits = (value, unitArray, divisor, initialUnit, preferredUnit) => {
    const sliceIndex = initialUnit ? unitArray.indexOf(initialUnit) : 0;
    const units_ = unitArray.slice(sliceIndex);
    if (preferredUnit || preferredUnit === '') {
        const unitIndex = units_.indexOf(preferredUnit);
        if (unitIndex !== -1) {
            return {
                value: value / divisor ** unitIndex,
                unit: preferredUnit,
            };
        }
    }
    let unit = units_.shift();
    while (value >= divisor && units_.length > 0) {
        value = value / divisor;
        unit = units_.shift();
    }
    return { value, unit };
};
const convertValueWithUnitsToBaseValue = (value, unitArray, divisor) => {
    const defaultReturn = { value: 0, unit: undefined };
    if (typeof value !== 'string') {
        return defaultReturn;
    }
    let units_ = unitArray.slice().reverse();
    // find which unit we're given
    let truncateStringAt = -1;
    const startingUnitIndex = (0, findIndex_js_1.default)(units_, function (currentUnitValue) {
        const index = value.indexOf(currentUnitValue);
        if (index > -1) {
            truncateStringAt = index;
            return true;
        }
        return false;
    });
    if (startingUnitIndex <= 0) {
        // can't parse
        return defaultReturn;
    }
    // get the numeric value & prepare unit array for conversion
    units_ = units_.slice(startingUnitIndex);
    value = value.substring(0, truncateStringAt);
    let numValue = (0, toNumber_js_1.default)(value);
    let unit = units_.shift();
    while (units_.length > 0) {
        numValue = numValue * divisor;
        unit = units_.shift();
    }
    return { value: numValue, unit };
};
const getDefaultFractionDigits = (value) => {
    if (value < 1) {
        return 3;
    }
    if (value < 100) {
        return 2;
    }
    return 1;
};
const formatValue = (value, options) => {
    const fractionDigits = getDefaultFractionDigits(value);
    const _a = (0, defaults_js_1.default)(options, {
        maximumFractionDigits: fractionDigits,
    }), { locales } = _a, rest = tslib_1.__rest(_a, ["locales"]);
    // 2nd check converts -0 to 0.
    if (!isFinite(value) || value === 0) {
        value = 0;
    }
    return Intl.NumberFormat(locales, rest).format(value);
};
const round = (value, fractionDigits = 10) => {
    if (!isFinite(value)) {
        return 0;
    }
    const multiplier = Math.pow(10, fractionDigits || getDefaultFractionDigits(value));
    return Math.round(value * multiplier) / multiplier;
};
const humanize = (value, typeName, useRound = false, initialUnit, preferredUnit) => {
    const type = (0, exports.getType)(typeName);
    if (!isFinite(value)) {
        value = 0;
    }
    let converted = convertBaseValueToUnits(value, type.units, type.divisor, initialUnit, preferredUnit);
    if (useRound) {
        converted.value = round(converted.value);
        converted = convertBaseValueToUnits(converted.value, type.units, type.divisor, converted.unit, preferredUnit);
    }
    const formattedValue = formatValue(converted.value);
    return {
        string: `${formattedValue}${type.space ? ' ' : ''}${converted.unit || ''}`,
        unit: converted.unit,
        value: converted.value,
    };
};
const dehumanize = (value, typeName) => {
    const type = (0, exports.getType)(typeName);
    return convertValueWithUnitsToBaseValue(value, type.units, type.divisor);
};
const formatPercentage = (value, options) => {
    const _a = (0, defaults_js_1.default)({ style: 'percent' }, // Don't allow perent style to be overridden.
    options, {
        maximumFractionDigits: 1,
    }), { locales } = _a, rest = tslib_1.__rest(_a, ["locales"]);
    return Intl.NumberFormat(locales, rest).format(value);
};
const humanizeBinaryBytesWithoutB = (v, initialUnit, preferredUnit) => humanize(v, 'binaryBytesWithoutB', true, initialUnit, preferredUnit);
exports.humanizeBinaryBytesWithoutB = humanizeBinaryBytesWithoutB;
const humanizeBinaryBytes = (v, initialUnit, preferredUnit) => humanize(v, 'binaryBytes', true, initialUnit, preferredUnit);
exports.humanizeBinaryBytes = humanizeBinaryBytes;
const humanizeDecimalBytes = (v, initialUnit, preferredUnit) => humanize(v, 'decimalBytes', true, initialUnit, preferredUnit);
exports.humanizeDecimalBytes = humanizeDecimalBytes;
const humanizeDecimalBytesPerSec = (v, initialUnit, preferredUnit) => humanize(v, 'decimalBytesPerSec', true, initialUnit, preferredUnit);
exports.humanizeDecimalBytesPerSec = humanizeDecimalBytesPerSec;
const humanizePacketsPerSec = (v, initialUnit, preferredUnit) => humanize(v, 'packetsPerSec', true, initialUnit, preferredUnit);
exports.humanizePacketsPerSec = humanizePacketsPerSec;
const humanizeNumber = (v, initialUnit, preferredUnit) => humanize(v, 'numeric', true, initialUnit, preferredUnit);
exports.humanizeNumber = humanizeNumber;
const humanizeNumberSI = (v, initialUnit, preferredUnit) => humanize(v, 'SI', true, initialUnit, preferredUnit);
exports.humanizeNumberSI = humanizeNumberSI;
const humanizeSeconds = (v, initialUnit, preferredUnit) => humanize(v, 'seconds', true, initialUnit, preferredUnit);
exports.humanizeSeconds = humanizeSeconds;
const humanizeCpuCores = (v) => {
    const value = v < 1 ? round(v * 1000) : v;
    const unit = v < 1 ? 'm' : '';
    return {
        string: `${formatValue(value)}${unit}`,
        unit,
        value,
    };
};
exports.humanizeCpuCores = humanizeCpuCores;
const humanizePercentage = (value) => {
    // 2nd check converts -0 to 0.
    if (!isFinite(value) || value === 0) {
        value = 0;
    }
    return {
        string: formatPercentage(value / 100),
        unit: '%',
        value: round(value, 1),
    };
};
exports.humanizePercentage = humanizePercentage;
const baseUnitedValidation = (value) => {
    if (value === null || value.length === 0) {
        return;
    }
    if (value.search(/\s/g) !== -1) {
        return 'white space is not allowed';
    }
};
const validateNumber = (float) => {
    if (float < 0) {
        return 'must be positive';
    }
    if (!isFinite(float)) {
        return 'must be a number';
    }
};
const validCPUUnits = new Set(['m', '']);
const validateCPUUnit = (value = '') => {
    if (validCPUUnits.has(value)) {
        return;
    }
    return `unrecongnized unit: ${value}`;
};
const validMemUnits = new Set(['E', 'P', 'T', 'G', 'M', 'k', 'Pi', 'Ti', 'Gi', 'Mi', 'Ki']);
const validateMemUnit = (value = '') => {
    if (validMemUnits.has(value)) {
        return;
    }
    return `unrecongnized unit: ${value}`;
};
const validTimeUnits = new Set(['s', 'm', 'h', 'd', 'M', 'y']);
const validateTimeUnit = (value = '') => {
    if (validTimeUnits.has(value)) {
        return;
    }
    return `unrecongnized unit: ${value}`;
};
exports.units = {
    round,
    humanize,
    dehumanize,
};
exports.validate = {
    split: (value) => {
        const index = value.search(/([a-zA-Z]+)/g);
        let number, unit;
        if (index === -1) {
            number = value;
        }
        else {
            number = value.slice(0, index);
            unit = value.slice(index);
        }
        return [parseFloat(number), unit];
    },
    CPU: (value = '') => {
        if (!value) {
            return;
        }
        const error = baseUnitedValidation(value);
        if (error) {
            return error;
        }
        const [num, unit] = exports.validate.split(value);
        const number = parseFloat((num || ''));
        if (!unit) {
            return validateNumber(number);
        }
        return validateNumber(number) || validateCPUUnit(unit);
    },
    time: (value = '') => {
        if (!value) {
            return;
        }
        const error = baseUnitedValidation(value);
        if (error) {
            return error;
        }
        const [num, unit] = exports.validate.split(value);
        const number = parseFloat((num || ''));
        if (!unit) {
            return 'number and unit required';
        }
        return validateNumber(number) || validateTimeUnit(unit);
    },
    memory: (value = '') => {
        if (!value) {
            return;
        }
        const error = baseUnitedValidation(value);
        if (error) {
            return error;
        }
        const [num, unit] = exports.validate.split(value);
        const number = parseFloat((num || ''));
        if (!unit) {
            return validateNumber(parseFloat(value));
        }
        return validateNumber(number) || validateMemUnit(unit);
    },
};
// Convert k8s compute resources values to a base value for comparison.
// If the value has no unit, it just returns the number, so this function
// can be used for any quota resource (resource counts). `units.dehumanize`
// is problematic for comparing quota resources because you need to know
// what unit you're dealing with already (e.g. decimal vs binary). Returns
// null if value isn't recognized as valid.
const convertToBaseValue = (value) => {
    if (!(0, isString_js_1.default)(value)) {
        return null;
    }
    const [num, unit] = exports.validate.split(value);
    const number = parseFloat((num || ''));
    const validationError = validateNumber(number);
    if (validationError) {
        return null;
    }
    if (!unit) {
        return number;
    }
    // Handle CPU millicores specifically.
    if (unit === 'm') {
        return number / 1000;
    }
    if (TYPES.binaryBytesWithoutB.units.includes(unit)) {
        return dehumanize(value, 'binaryBytesWithoutB').value;
    }
    if (TYPES.decimalBytesWithoutB.units.includes(unit)) {
        return dehumanize(value, 'decimalBytesWithoutB').value;
    }
    // Unrecognized unit.
    return null;
};
exports.convertToBaseValue = convertToBaseValue;
const secondsToNanoSeconds = (value) => {
    const val = Number(value);
    return Number.isFinite(val) ? val * 1000 ** 3 : 0;
};
exports.secondsToNanoSeconds = secondsToNanoSeconds;
const formatToFractionalDigits = (value, digits) => Intl.NumberFormat(undefined, {
    minimumFractionDigits: digits,
    maximumFractionDigits: digits,
}).format(value);
exports.formatToFractionalDigits = formatToFractionalDigits;
const formatBytesAsMiB = (bytes) => {
    const mib = bytes / 1024 / 1024;
    return (0, exports.formatToFractionalDigits)(mib, 1);
};
exports.formatBytesAsMiB = formatBytesAsMiB;
const formatBytesAsGiB = (bytes) => {
    const gib = bytes / 1024 / 1024 / 1024;
    return (0, exports.formatToFractionalDigits)(gib, 2);
};
exports.formatBytesAsGiB = formatBytesAsGiB;
const formatCores = (cores) => (0, exports.formatToFractionalDigits)(cores, 3);
exports.formatCores = formatCores;
//# sourceMappingURL=units.js.map