"use strict";
/* eslint-disable no-nested-ternary */
Object.defineProperty(exports, "__esModule", { value: true });
exports.inBetween = exports.isNil = exports.asPlain = exports.asFn = exports.deepIsEqual = exports.get = exports.html = exports.px = exports.debounce = exports.exec = exports.preprocess = exports.genId = exports.findDataIdx = exports.toFixed = exports.getUnitSuffix = exports.findSticky = exports.getSumByIdx = exports.findInRange = void 0;
/**
 * Finds index of point in ranges of Y-Axis values.
 * Returns index of starting range when idx < Y <= idx next
 *
 * @param {TooltipSection} section - tooltip section
 * @param {number} value - Y value of cursor
 * @param {boolean} stickToRanges - if true, then always return index of range
 * @returns {number | null}
 */
const findInRange = (section, value, stickToRanges = true) => {
    const positive = value >= 0;
    let max = -Infinity, maxIdx = null;
    let min = Infinity, minIdx = null;
    const diffs = [];
    let result = null;
    for (let r = section.rows.length - 1; r >= 0; r--) {
        const row = section.rows[r];
        const { displayY: y, rowIdx } = row;
        let diff;
        if (y !== null) {
            if (y > max) {
                max = y;
                maxIdx = row.rowIdx;
            }
            if (y < min) {
                min = y;
                minIdx = row.rowIdx;
            }
        }
        if (y === null || (positive ? y < 0 : y >= 0)) {
            diff = null;
        }
        else if (positive) {
            diff = value > y ? null : y - value;
        }
        else {
            diff = value < y ? null : Math.abs(y - value);
        }
        const currentMin = result === null ? Infinity : diffs[result];
        const nextMin = diff === null ? currentMin : Math.min(currentMin, diff);
        if ((diff !== null && currentMin === diff) || nextMin !== currentMin) {
            result = rowIdx;
        }
    }
    if (result === null && stickToRanges) {
        return value >= max ? maxIdx : value <= min ? minIdx : null;
    }
    return result;
};
exports.findInRange = findInRange;
/* Gets sum of all values of given data index by all series */
const getSumByIdx = (seriesOptions, idx, scale) => {
    let sum = 0;
    let i = 0;
    while (i < seriesOptions.length) {
        const seriesIdx = seriesOptions.length - i - 1;
        const opts = seriesOptions[seriesIdx];
        const seriesValues = opts.$c;
        i += 1;
        if (opts.scale !== scale || opts.show === false) {
            continue;
        }
        const value = seriesValues[idx];
        sum += typeof value === 'number' ? value : 0;
    }
    return sum;
};
exports.getSumByIdx = getSumByIdx;
/**
 * Finds index of nearest non-null point in range of Y-Axis values
 *
 * @param {TooltipSection} section
 * @param {number} value
 * @returns {number | null}
 */
const findSticky = (section, value) => {
    let nearestIndex;
    let nearestValue;
    let i = 0;
    while (!nearestValue && i < section.rows.length) {
        const r = section.rows[i].displayY;
        if (r !== null) {
            nearestIndex = i;
            nearestValue = Math.abs(r - (value || 0));
        }
        i += 1;
    }
    if (!nearestValue || nearestIndex === undefined) {
        return null;
    }
    for (i = nearestIndex + 1; i < section.rows.length; i++) {
        const v = section.rows[i].displayY;
        if (v === null) {
            continue;
        }
        const diff = Math.abs(v - value);
        if (nearestValue > diff) {
            nearestValue = diff;
            nearestIndex = i;
        }
    }
    return nearestIndex;
};
exports.findSticky = findSticky;
const getUnitSuffix = (value) => {
    if (value >= 1e18) {
        return [1e18, 'E'];
    }
    else if (value >= 1e15) {
        return [1e15, 'P'];
    }
    else if (value >= 1e12) {
        return [1e12, 'T'];
    }
    else if (value >= 1e9) {
        return [1e9, 'G'];
    }
    else if (value >= 1e6) {
        return [1e6, 'M'];
    }
    else if (value >= 1e3) {
        return [1e3, 'K'];
    }
    return [1, ''];
};
exports.getUnitSuffix = getUnitSuffix;
/* Number.toFixed() wihout rounding */
function toFixed(num, fixed) {
    if (fixed === 0) {
        return parseInt(num);
    }
    if (Number.isInteger(num)) {
        return num + '.' + '0'.repeat(fixed);
    }
    const [int, frac] = num.toString().split('.');
    return frac.length >= fixed ? `${int}.${frac.slice(0, fixed)}` : `${int}.${frac}${'0'.repeat(fixed - frac.length)}`;
}
exports.toFixed = toFixed;
/**
 * Finds nearest non-null value's index in data series by given direction
 *
 * @param {DataSeriesExtended} data - Series data
 * @param {Series} series - Series options
 * @param {number} idx - cursor index
 * @param {SnapToValue | false} defaultSnapTo - default value for direction
 * @param {unknown} skipValue - value to skip
 * @returns {number}
 */
function findDataIdx(data, series, idx, defaultSnapTo = 'closest', skipValue = null) {
    var _a;
    let corL = idx, corR = idx;
    const direction = (_a = series.snapToValues) !== null && _a !== void 0 ? _a : defaultSnapTo;
    if (direction === false) {
        return idx;
    }
    if (direction === 'left' || direction === 'closest') {
        for (let i = idx - 1; i >= 0; i--) {
            if (data[i] !== skipValue) {
                corL = i;
                break;
            }
        }
    }
    if (direction === 'right' || direction === 'closest') {
        for (let i = idx + 1; i < data.length; i++) {
            if (data[i] !== skipValue) {
                corR = i;
                break;
            }
        }
    }
    if (direction === 'left') {
        return corL;
    }
    if (direction === 'right') {
        return corR;
    }
    return corR - idx > idx - corL ? corL : corR;
}
exports.findDataIdx = findDataIdx;
/*
 * Interpolation function
 */
const interpolateImpl = (timeline, y1, y2, x1, x2, xIdx, iGroup, type = 'linear') => {
    let result = null;
    const x = timeline[xIdx];
    switch (type) {
        case 'linear': {
            if (y1 === null || y2 === null) {
                return null;
            }
            result = y1 + ((x - x1) * (y2 - y1)) / (x2 - x1);
            if (isNaN(result) || Math.abs(result) === Infinity) {
                result = null;
            }
            break;
        }
        case 'previous': {
            result = y1;
            break;
        }
        case 'next': {
            result = y2;
            break;
        }
        case 'left': {
            result = iGroup[iGroup.length - 1] === timeline.length - 1 || y2 === null ? null : y1;
            break;
        }
        case 'right': {
            result = iGroup[0] === 0 ? null : y2;
            break;
        }
        case 'closest': {
            const lD = Math.abs(x1 - timeline[xIdx]);
            const rD = Math.abs(x2 - timeline[xIdx]);
            result = lD < rD ? y1 : y2;
            break;
        }
        default: {
            result = type;
        }
    }
    return result;
};
// https://stackoverflow.com/a/53116778
const genId = () => Date.now().toString(36) + Math.random().toString(36).substring(2);
exports.genId = genId;
/**
 * Processing data series to:
 *  1. Find missing data and interpolate these points
 *  2. Find string special values to convert them to nulls
 *
 * @param {DataSeriesExtended[]} series
 * @param {number[]} timeline
 * @param {ProcessingSettings} settings
 * @returns {DataSeries[]}
 */
const preprocess = (series, timeline, settings) => {
    const result = [];
    const nullValues = settings.nullValues || {};
    const interpolation = settings.interpolation;
    for (let sIdx = 0; sIdx < series.length; sIdx++) {
        const line = series[sIdx];
        const resultLine = [];
        let iGroup = [];
        let y1 = null, y2 = null, x1, x2;
        for (let idx = 0; idx < line.length; idx++) {
            let val = line[idx];
            if (interpolation && val === interpolation.value) {
                iGroup.push(idx);
                continue;
            }
            if (nullValues[val]) {
                val = null;
            }
            if (iGroup.length) {
                y2 = val;
                x2 = timeline[idx];
                for (const iIdx of iGroup) {
                    resultLine[iIdx] = interpolateImpl(timeline, y1, y2, x1 || timeline[0], x2 || timeline[timeline.length - 1], iIdx, iGroup, interpolation && interpolation.type);
                }
                iGroup = [];
            }
            y1 = val;
            x1 = timeline[idx];
            resultLine.push(val);
        }
        y2 = null;
        if (iGroup.length) {
            for (const iIdx of iGroup) {
                resultLine.push(interpolateImpl(timeline, y1, y2, x1 || timeline[0], x2 || timeline[timeline.length - 1], iIdx, iGroup, interpolation && interpolation.type));
            }
        }
        result.push(resultLine);
    }
    return result;
};
exports.preprocess = preprocess;
const exec = (s, ...args) => {
    return typeof s === 'function' ? s(...args) : s;
};
exports.exec = exec;
function debounce(func, timeout = 300) {
    let timer;
    return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => func(...args), timeout);
    };
}
exports.debounce = debounce;
const px = (x) => x + 'px';
exports.px = px;
const html = (tag, attrs = {}, content) => {
    const el = document.createElement(tag);
    Object.keys(attrs).forEach((key) => {
        const attr = attrs[key];
        el.setAttribute(key, typeof attr === 'object'
            ? Object.entries(attr)
                .map(([a, b]) => `${a}:${b}`)
                .join(';')
            : attr);
    });
    if (content) {
        if (typeof content === 'string') {
            el.innerHTML = content;
        }
        else {
            el.appendChild(content);
        }
    }
    return el;
};
exports.html = html;
function get(obj, key) {
    return key.split('.').reduce((acc, key) => { var _a, _b; return (_b = (_a = Object.getOwnPropertyDescriptor(acc, key)) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : {}; }, obj);
}
exports.get = get;
function deepIsEqual(a, b) {
    if (typeof a !== typeof b) {
        return false;
    }
    if (typeof a === 'function' || typeof b === 'function') {
        a = a.toString();
        b = b.toString();
    }
    if (typeof a !== 'object' || isNil(a) || isNil(b)) {
        return a === b;
    }
    const aObject = a;
    const bObject = b;
    const aKeys = Object.keys(aObject);
    const bKeys = Object.keys(bObject);
    if (aKeys.length !== bKeys.length) {
        return false;
    }
    for (const key of aKeys) {
        if (!bObject.hasOwnProperty(key)) {
            return false;
        }
        if (!deepIsEqual(aObject[key], bObject[key])) {
            return false;
        }
    }
    return true;
}
exports.deepIsEqual = deepIsEqual;
function asFn(t) {
    return typeof t === 'function' ? t : typeof t === 'undefined' ? t : () => t;
}
exports.asFn = asFn;
function asPlain(t) {
    return typeof t === 'function' ? t() : t;
}
exports.asPlain = asPlain;
function isNil(v) {
    return v === null || v === undefined;
}
exports.isNil = isNil;
function inBetween(value, start, end) {
    return value >= start && value <= end ? value : value < start ? start : end;
}
exports.inBetween = inBetween;
