"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SERIES_DELIMITER = void 0;
exports.getSeriesIndex = getSeriesIndex;
exports.getAccessorFieldName = getAccessorFieldName;
exports.splitSeriesDataByAccessors = splitSeriesDataByAccessors;
exports.getSeriesKey = getSeriesKey;
exports.extractYAndMarkFromDatum = extractYAndMarkFromDatum;
exports.getFormattedDataSeries = getFormattedDataSeries;
exports.getDataSeriesFromSpecs = getDataSeriesFromSpecs;
exports.isBandedSpec = isBandedSpec;
exports.getSeriesName = getSeriesName;
exports.getSeriesColors = getSeriesColors;
exports.getSeriesIdentifierFromDataSeries = getSeriesIdentifierFromDataSeries;
const fit_function_utils_1 = require("./fit_function_utils");
const group_data_series_1 = require("./group_data_series");
const stacked_series_utils_1 = require("./stacked_series_utils");
const colors_1 = require("../../../common/colors");
const constants_1 = require("../../../scales/constants");
const specs_1 = require("../../../specs");
const accessor_1 = require("../../../utils/accessor");
const common_1 = require("../../../utils/common");
const logger_1 = require("../../../utils/logger");
const y_domain_1 = require("../domains/y_domain");
const scale_defaults_1 = require("../scales/scale_defaults");
exports.SERIES_DELIMITER = ' - ';
function getSeriesIndex(series, target) {
    if (!series) {
        return -1;
    }
    return series.findIndex(({ key }) => target.key === key);
}
function getAccessorFieldName(accessor, index) {
    return typeof accessor === 'function' ? accessor.fieldName ?? `(index:${index})` : accessor;
}
function splitSeriesDataByAccessors(spec, xValueSums, insertOrder = 0, isStacked = false, isBanded = false, stackMode, groupBySpec, deselectedDataSeries = []) {
    const { seriesType, id: specId, groupId, data, xAccessor, yAccessors, y0Accessors, markSizeAccessor, splitSeriesAccessors = [], } = spec;
    const dataSeries = new Map();
    const xValues = [];
    const nonNumericValues = new Map();
    if (isStacked && Boolean(y0Accessors?.length)) {
        logger_1.Logger.warn(`y0Accessors are not allowed with stackAccessors. y0Accessors will be ignored but available under initialY0.`);
    }
    for (let i = 0; i < data.length; i++) {
        const datum = data[i];
        const splitAccessors = getSplitAccessors(datum, splitSeriesAccessors);
        if (splitSeriesAccessors.length > 0 && splitAccessors.size < 1) {
            continue;
        }
        if (typeof datum !== 'object' || datum === null) {
            continue;
        }
        const x = (0, accessor_1.getAccessorValue)(datum, xAccessor);
        if (typeof x !== 'string' && typeof x !== 'number') {
            continue;
        }
        xValues.push(x);
        let sum = xValueSums.get(x) ?? 0;
        const smH = groupBySpec?.horizontal?.by?.(spec, datum);
        const smV = groupBySpec?.vertical?.by?.(spec, datum);
        const xAccessorStr = getAccessorFieldName(xAccessor, 0);
        yAccessors.forEach((accessor, index) => {
            const cleanedDatum = extractYAndMarkFromDatum(datum, accessor, nonNumericValues, isBanded, y0Accessors && y0Accessors[index], markSizeAccessor);
            const yAccessorStr = getAccessorFieldName(accessor, index);
            const splitAccessorStrs = [...splitAccessors.values()].map((a, si) => getAccessorFieldName(a, si));
            const seriesKeys = [...splitAccessorStrs, yAccessorStr];
            const seriesIdentifier = (0, common_1.stripUndefined)({
                specId,
                seriesKeys,
                xAccessor: xAccessorStr,
                yAccessor: yAccessorStr,
                splitAccessors,
                smVerticalAccessorValue: smV,
                smHorizontalAccessorValue: smH,
            });
            const seriesKey = getSeriesKey(seriesIdentifier, groupId);
            sum += cleanedDatum.y1 ?? 0;
            const newDatum = { x, ...cleanedDatum, smH, smV };
            const series = dataSeries.get(seriesKey);
            if (series) {
                series.data.push(newDatum);
            }
            else {
                dataSeries.set(seriesKey, {
                    ...seriesIdentifier,
                    groupId,
                    seriesType,
                    stackMode,
                    isStacked,
                    seriesKeys,
                    key: seriesKey,
                    data: [newDatum],
                    spec,
                    insertOrder: insertOrder + dataSeries.size,
                    sortOrder: 0,
                    isFiltered: deselectedDataSeries.some(({ key: deselectedKey }) => seriesKey === deselectedKey),
                });
            }
            xValueSums.set(x, sum);
        });
    }
    if (nonNumericValues.size > 0) {
        const values = [...nonNumericValues.entries()];
        const total = values.reduce((sum, [, v]) => sum + v, 0);
        logger_1.Logger.warn(`Found ${total} non-numeric y value${total > 1 ? 's' : ''} in dataset for spec "${specId}"`, `(${values.map(([k, v]) => `${v}: ${JSON.stringify(k)}`).join(', ')})`);
    }
    return {
        dataSeries,
        xValues,
    };
}
function getSeriesKey({ specId, yAccessor, splitAccessors, smVerticalAccessorValue, smHorizontalAccessorValue, }, groupId) {
    const joinedAccessors = [...splitAccessors.entries()]
        .sort(([a], [b]) => (a > b ? 1 : -1))
        .map(([key, value]) => `${key}-${value}`)
        .join('|');
    const smV = smVerticalAccessorValue ? `smV{${smVerticalAccessorValue}}` : '';
    const smH = smHorizontalAccessorValue ? `smH{${smHorizontalAccessorValue}}` : '';
    return `groupId{${groupId}}spec{${specId}}yAccessor{${yAccessor}}splitAccessors{${joinedAccessors}}${smV}${smH}`;
}
function getSplitAccessors(datum, accessors = []) {
    const splitAccessors = new Map();
    if (typeof datum === 'object' && datum !== null) {
        accessors.forEach((accessor, index) => {
            const value = (0, accessor_1.getAccessorValue)(datum, accessor);
            if (typeof value === 'string' || typeof value === 'number') {
                const accessorStr = getAccessorFieldName(accessor, index);
                splitAccessors.set(accessorStr, value);
            }
        });
    }
    return splitAccessors;
}
function extractYAndMarkFromDatum(datum, yAccessor, nonNumericValues, bandedSpec, y0Accessor, markSizeAccessor) {
    const mark = markSizeAccessor === undefined ? null : finiteOrNull((0, accessor_1.getAccessorValue)(datum, markSizeAccessor), nonNumericValues);
    const y1Value = (0, accessor_1.getAccessorValue)(datum, yAccessor);
    const y1 = finiteOrNull(y1Value, nonNumericValues);
    const y0 = y0Accessor ? finiteOrNull((0, accessor_1.getAccessorValue)(datum, y0Accessor), nonNumericValues) : null;
    return { y1, datum, y0: bandedSpec ? y0 : null, mark, initialY0: y0, initialY1: y1 };
}
function finiteOrNull(value, nonNumericValues) {
    const candidateNumber = Number(value ?? undefined);
    const finite = Number.isFinite(candidateNumber);
    if (!finite) {
        const val = nonNumericValues.get(value) ?? 0;
        nonNumericValues.set(value, val + 1);
    }
    return finite ? candidateNumber : null;
}
const getSortedDataSeries = (dataSeries, xValues, xScaleType) => dataSeries.map(({ data, ...rest }) => ({
    ...rest,
    data: data.slice().sort((0, stacked_series_utils_1.datumXSortPredicate)(xScaleType, [...xValues.values()])),
}));
function getFormattedDataSeries(seriesSpecs, availableDataSeries, xValues, xScaleType) {
    const fittedDataSeries = (0, fit_function_utils_1.applyFitFunctionToDataSeries)(getSortedDataSeries(availableDataSeries, xValues, xScaleType), seriesSpecs, xScaleType);
    const stackedDataSeries = fittedDataSeries.filter(({ spec }) => (0, y_domain_1.isStackedSpec)(spec));
    const stackedGroups = (0, group_data_series_1.groupBy)(stackedDataSeries, ['smHorizontalAccessorValue', 'smVerticalAccessorValue', 'groupId'], true);
    const fittedAndStackedDataSeries = stackedGroups.reduce((acc, dataSeries) => {
        if (!dataSeries[0])
            return acc;
        const [{ stackMode, seriesType }] = dataSeries;
        const formatted = (0, stacked_series_utils_1.formatStackedDataSeriesValues)(dataSeries, xValues, seriesType, stackMode);
        return [...acc, ...formatted];
    }, []);
    const nonStackedDataSeries = fittedDataSeries.filter(({ spec }) => !(0, y_domain_1.isStackedSpec)(spec));
    return [...fittedAndStackedDataSeries, ...nonStackedDataSeries].sort((a, b) => a.sortOrder - b.sortOrder);
}
function getDataSeriesFromSpecs(seriesSpecs, deselectedDataSeries = [], orderOrdinalBinsBy, seriesSort, groupBySpec) {
    const globalDataSeries = [];
    const mutatedXValueSums = new Map();
    const globalXValues = new Set();
    let isNumberArray = true;
    let isOrdinalScale = false;
    const specsByYGroup = (0, y_domain_1.groupSeriesByYGroup)(seriesSpecs);
    let insertOrder = 0;
    for (const spec of seriesSpecs) {
        if (spec.xScaleType === constants_1.ScaleType.Ordinal) {
            isOrdinalScale = true;
        }
        const specGroup = specsByYGroup.get(spec.groupId);
        const isStacked = Boolean(specGroup?.stacked.find(({ id }) => id === spec.id));
        const isBanded = isBandedSpec(spec);
        const { dataSeries, xValues } = splitSeriesDataByAccessors(spec, mutatedXValueSums, insertOrder, isStacked, isBanded, specGroup?.stackMode, groupBySpec, deselectedDataSeries);
        insertOrder += dataSeries.size;
        for (const [, ds] of dataSeries) {
            globalDataSeries.push(ds);
        }
        for (const xValue of xValues) {
            if (isNumberArray && typeof xValue !== 'number') {
                isNumberArray = false;
            }
            globalXValues.add(xValue);
        }
    }
    const xValues = isOrdinalScale || !isNumberArray
        ? getSortedOrdinalXValues(globalXValues, mutatedXValueSums, orderOrdinalBinsBy)
        : new Set([...globalXValues].sort((a, b) => {
            if (typeof a === 'string' || typeof b === 'string') {
                return 0;
            }
            return a - b;
        }));
    globalDataSeries.sort(seriesSort).map((d, i) => {
        d.sortOrder = i;
        return d;
    });
    const smallMultipleUniqueValues = globalDataSeries.reduce((acc, curr) => {
        if (curr.isFiltered) {
            return acc;
        }
        if (!(0, common_1.isNil)(curr.smHorizontalAccessorValue)) {
            acc.smHValues.add(curr.smHorizontalAccessorValue);
        }
        if (!(0, common_1.isNil)(curr.smVerticalAccessorValue)) {
            acc.smVValues.add(curr.smVerticalAccessorValue);
        }
        return acc;
    }, { smVValues: new Set(), smHValues: new Set() });
    return {
        dataSeries: globalDataSeries,
        xValues,
        ...smallMultipleUniqueValues,
        fallbackScale: !isOrdinalScale && !isNumberArray ? scale_defaults_1.X_SCALE_DEFAULT.type : undefined,
    };
}
function isBandedSpec(spec) {
    return Boolean(spec.y0Accessors && spec.y0Accessors.length > 0 && !(0, y_domain_1.isStackedSpec)(spec));
}
function getSortedOrdinalXValues(xValues, xValueSums, orderOrdinalBinsBy) {
    if (!orderOrdinalBinsBy) {
        return xValues;
    }
    switch (orderOrdinalBinsBy?.binAgg) {
        case specs_1.BinAgg.None:
            return xValues;
        case specs_1.BinAgg.Sum:
        default:
            return new Set([...xValues].sort((v1, v2) => (orderOrdinalBinsBy.direction === specs_1.Direction.Ascending ? 1 : -1) *
                ((xValueSums.get(v1) ?? 0) - (xValueSums.get(v2) ?? 0))));
    }
}
const BIG_NUMBER = Number.MAX_SAFE_INTEGER;
function getSeriesNameFromOptions(options, { yAccessor, splitAccessors }, delimiter) {
    if (!options.names) {
        return null;
    }
    return ([...options.names]
        .sort(({ sortIndex: a = BIG_NUMBER }, { sortIndex: b = BIG_NUMBER }) => a - b)
        .map(({ accessor, value, name }) => {
        const accessorValue = splitAccessors.get(accessor) ?? null;
        if (accessorValue === value) {
            return name ?? value;
        }
        if (yAccessor === accessor) {
            return name ?? accessor;
        }
        return null;
    })
        .filter((d) => Boolean(d) || d === 0)
        .join(delimiter) || null);
}
function getSeriesName(seriesIdentifier, hasSingleSeries, isTooltip, spec) {
    const customLabel = typeof spec?.name === 'function'
        ? spec.name(seriesIdentifier, isTooltip)
        : typeof spec?.name === 'object'
            ? getSeriesNameFromOptions(spec.name, seriesIdentifier, spec.name.delimiter ?? exports.SERIES_DELIMITER)
            : null;
    if (customLabel !== null) {
        return customLabel.toString();
    }
    const multipleYAccessors = spec && spec.yAccessors.length > 1;
    const nameKeys = multipleYAccessors ? seriesIdentifier.seriesKeys : seriesIdentifier.seriesKeys.slice(0, -1);
    const nonZeroLength = nameKeys.length > 0;
    return nonZeroLength && (spec?.splitSeriesAccessors || !hasSingleSeries)
        ? nameKeys.join(typeof spec?.name === 'object' ? spec.name.delimiter ?? exports.SERIES_DELIMITER : exports.SERIES_DELIMITER)
        : spec === undefined
            ? ''
            : typeof spec.name === 'string'
                ? spec.name
                : spec.id;
}
function getHighestOverride(key, customColors, overrides) {
    const tempColor = overrides.temporary[key];
    return tempColor || customColors.get(key) || (tempColor === null ? undefined : overrides.persisted[key]);
}
function getSeriesColors(dataSeries, chartColors, customColors, overrides) {
    const seriesColorMap = new Map();
    let counter = 0;
    (0, group_data_series_1.groupBy)(dataSeries, (ds) => {
        return [ds.specId, ds.groupId, ds.yAccessor, ...ds.splitAccessors.values()].join('__');
    }, true).forEach(([ds]) => {
        if (!ds)
            return;
        const seriesKey = getSeriesKey({
            specId: ds.specId,
            yAccessor: ds.yAccessor,
            splitAccessors: ds.splitAccessors,
        }, ds.groupId);
        const colorOverride = getHighestOverride(seriesKey, customColors, overrides);
        const color = colorOverride || chartColors.vizColors[counter % chartColors.vizColors.length] || colors_1.Colors.White.keyword;
        seriesColorMap.set(seriesKey, color);
        counter++;
    });
    return seriesColorMap;
}
function getSeriesIdentifierFromDataSeries(dataSeries) {
    const { key, specId, seriesKeys, xAccessor, yAccessor, splitAccessors, smVerticalAccessorValue, smHorizontalAccessorValue, } = dataSeries;
    return (0, common_1.stripUndefined)({
        key,
        specId,
        seriesKeys,
        xAccessor,
        yAccessor,
        splitAccessors,
        smVerticalAccessorValue,
        smHorizontalAccessorValue,
    });
}
//# sourceMappingURL=series.js.map