/**
 * DevExtreme (esm/viz/components/data_validator.js)
 * Version: 24.1.7-build-24272-1937
 * Build date: Sat Sep 28 2024
 *
 * Copyright (c) 2012 - 2024 Developer Express Inc. ALL RIGHTS RESERVED
 * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
 */
import {
    isDefined as _isDefined,
    isFunction as _isFunction,
    isString as _isString,
    isDate as _isDate,
    isNumeric as _isNumber,
    isObject as _isObject
} from "../../core/utils/type";
const STRING = "string";
const NUMERIC = "numeric";
const DATETIME = "datetime";
const DISCRETE = "discrete";
const SEMIDISCRETE = "semidiscrete";
const CONTINUOUS = "continuous";
const LOGARITHMIC = "logarithmic";
const VALUE_TYPE = "valueType";
const ARGUMENT_TYPE = "argumentType";
import {
    extend
} from "../../core/utils/extend";
import {
    enumParser
} from "../core/utils";
const axisTypeParser = enumParser([STRING, NUMERIC, DATETIME]);
import {
    getParser as _getParser
} from "./parse_utils";
const _isArray = Array.isArray;

function groupingValues(data, others, valueField, index) {
    if (index >= 0) {
        data.slice(index).forEach((function(cell) {
            if (_isDefined(cell[valueField])) {
                others[valueField] += cell[valueField];
                cell[valueField] = void 0
            }
        }))
    }
}

function processGroups(groups) {
    groups.forEach((function(group) {
        group.valueType = group.valueAxisType = null;
        group.series.forEach((function(series) {
            series.updateDataType({})
        }));
        group.valueAxis && group.valueAxis.resetTypes(VALUE_TYPE)
    }))
}

function sortValues(data, asc, selector) {
    const func = asc ? function(a, b) {
        return a - b
    } : function(a, b) {
        return b - a
    };
    data.sort((function(a, b) {
        const valA = selector(a);
        const valB = selector(b);
        const aa = _isDefined(valA) ? 1 : 0;
        const bb = _isDefined(valB) ? 1 : 0;
        return aa && bb ? func(valA, valB) : func(aa, bb)
    }));
    return data
}

function resetArgumentAxes(axes) {
    axes && axes.forEach((function(axis) {
        axis.resetTypes(ARGUMENT_TYPE)
    }))
}

function parseCategories(categories, parser) {
    const newArray = [];
    categories.forEach((function(category) {
        const parsedCategory = parser(category);
        void 0 !== parsedCategory && newArray.push(parsedCategory)
    }));
    return newArray
}

function parseAxisCategories(groupsData, parsers) {
    const argumentCategories = groupsData.argumentOptions && groupsData.argumentOptions.categories;
    groupsData.groups.forEach((function(valueGroup, i) {
        const categories = valueGroup.valueOptions && valueGroup.valueOptions.categories;
        if (categories) {
            valueGroup.valueOptions.categories = parseCategories(categories, parsers[i + 1])
        }
    }));
    if (argumentCategories) {
        groupsData.argumentOptions.categories = parseCategories(argumentCategories, parsers[0])
    }
}

function eigen(x) {
    return x
}

function getType(unit, type) {
    let result = type;
    if (type === STRING || _isString(unit)) {
        result = STRING
    } else if (type === DATETIME || _isDate(unit)) {
        result = DATETIME
    } else if (_isNumber(unit)) {
        result = NUMERIC
    }
    return result
}

function correctAxisType(type, axisType, hasCategories, incidentOccurred) {
    if (type === STRING && (axisType === CONTINUOUS || axisType === LOGARITHMIC || axisType === SEMIDISCRETE)) {
        incidentOccurred("E2002")
    }
    return axisType === LOGARITHMIC ? LOGARITHMIC : hasCategories || axisType === DISCRETE || type === STRING ? DISCRETE : axisType === SEMIDISCRETE ? SEMIDISCRETE : CONTINUOUS
}

function validUnit(unit, field, incidentOccurred) {
    if (unit) {
        incidentOccurred(!_isNumber(unit) && !_isDate(unit) && !_isString(unit) ? "E2003" : "E2004", [field])
    }
}

function createParserUnit(type, axisType, incidentOccurred) {
    const parser = type ? _getParser(type) : eigen;
    const filterInfinity = axisType !== DISCRETE ? function(x) {
        return isFinite(x) || void 0 === x ? x : null
    } : eigen;
    return function(unit, field) {
        const parseUnit = filterInfinity(parser(unit));
        if (void 0 === parseUnit) {
            validUnit(unit, field, incidentOccurred)
        }
        return parseUnit
    }
}

function prepareParsers(groupsData, incidentOccurred) {
    const argumentParser = createParserUnit(groupsData.argumentType, groupsData.argumentAxisType, incidentOccurred);
    let sizeParser;
    let valueParser;
    const categoryParsers = [argumentParser];
    const cache = {};
    const list = [];
    groupsData.groups.forEach((function(group, groupIndex) {
        group.series.forEach((function(series) {
            valueParser = createParserUnit(group.valueType, group.valueAxisType, incidentOccurred);
            sizeParser = createParserUnit(NUMERIC, CONTINUOUS, incidentOccurred);
            cache[series.getArgumentField()] = argumentParser;
            series.getValueFields().forEach((function(field) {
                categoryParsers[groupIndex + 1] = valueParser;
                cache[field] = valueParser
            }));
            if (series.getSizeField()) {
                cache[series.getSizeField()] = sizeParser
            }
        }))
    }));
    for (const field in cache) {
        list.push([field, cache[field]])
    }
    list.length && parseAxisCategories(groupsData, categoryParsers);
    return list
}

function getParsedCell(cell, parsers) {
    let i;
    const ii = parsers.length;
    const obj = extend({}, cell);
    let field;
    let value;
    for (i = 0; i < ii; ++i) {
        field = parsers[i][0];
        value = cell[field];
        obj[field] = parsers[i][1](value, field)
    }
    return obj
}

function parse(data, parsers) {
    const parsedData = [];
    let i;
    const ii = data.length;
    parsedData.length = ii;
    for (i = 0; i < ii; ++i) {
        parsedData[i] = getParsedCell(data[i], parsers)
    }
    return parsedData
}

function findIndexByThreshold(data, valueField, threshold) {
    let i;
    const ii = data.length;
    let value;
    for (i = 0; i < ii; ++i) {
        value = data[i][valueField];
        if (_isDefined(value) && threshold > value) {
            break
        }
    }
    return i
}

function groupMinSlices(originalData, argumentField, valueField, smallValuesGrouping) {
    smallValuesGrouping = smallValuesGrouping || {};
    const mode = smallValuesGrouping.mode;
    const others = {};
    if (!mode || "none" === mode) {
        return
    }
    others[argumentField] = String(smallValuesGrouping.groupName || "others");
    others[valueField] = 0;
    const data = sortValues(originalData.slice(), false, (function(a) {
        return a[valueField]
    }));
    groupingValues(data, others, valueField, "smallValueThreshold" === mode ? findIndexByThreshold(data, valueField, smallValuesGrouping.threshold) : smallValuesGrouping.topCount);
    others[valueField] && originalData.push(others)
}

function groupPieData(data, groupsData) {
    const firstSeries = groupsData.groups[0] && groupsData.groups[0].series[0];
    const isPie = firstSeries && ("pie" === firstSeries.type || "doughnut" === firstSeries.type || "donut" === firstSeries.type);
    if (!isPie) {
        return
    }
    groupsData.groups.forEach((function(group) {
        group.series.forEach((function(series) {
            groupMinSlices(data, series.getArgumentField(), series.getValueFields()[0], series.getOptions().smallValuesGrouping)
        }))
    }))
}

function addUniqueItemToCollection(item, collection, itemsHash) {
    if (!itemsHash[item]) {
        collection.push(item);
        itemsHash[item] = true
    }
}

function getUniqueArgumentFields(groupsData) {
    const uniqueArgumentFields = [];
    const hash = {};
    groupsData.groups.forEach((function(group) {
        group.series.forEach((function(series) {
            addUniqueItemToCollection(series.getArgumentField(), uniqueArgumentFields, hash)
        }))
    }));
    return uniqueArgumentFields
}

function sort(a, b) {
    const result = a - b;
    if (isNaN(result)) {
        if (!_isDefined(a)) {
            return 1
        }
        if (!_isDefined(b)) {
            return -1
        }
        return 0
    }
    return result
}

function sortByArgument(data, argumentField) {
    return data.slice().sort((function(a, b) {
        return sort(a[argumentField], b[argumentField])
    }))
}

function sortByCallback(data, callback) {
    return data.slice().sort(callback)
}

function checkValueTypeOfGroup(group, cell) {
    group.series.forEach((function(series) {
        series.getValueFields().forEach((function(field) {
            group.valueType = getType(cell[field], group.valueType)
        }))
    }));
    return group.valueType
}

function getSortByCategories(categories) {
    const hash = {};
    categories.forEach((function(value, i) {
        hash[value] = i
    }));
    return function(data, argumentField) {
        return sortValues(data.slice(), true, (function(a) {
            return hash[a[argumentField]]
        }))
    }
}

function sortData(data, groupsData, options, uniqueArgumentFields) {
    const dataByArguments = {};
    const isDiscrete = groupsData.argumentAxisType === DISCRETE;
    const userCategories = isDiscrete && groupsData.argumentOptions && groupsData.argumentOptions.categories;
    let sortFunction = function(data) {
        return data
    };
    const sortingMethodOption = options.sortingMethod;
    let reSortCategories;
    if (!userCategories && _isFunction(sortingMethodOption)) {
        data = sortByCallback(data, sortingMethodOption)
    }
    if (isDiscrete) {
        groupsData.categories = getCategories(data, uniqueArgumentFields, userCategories)
    }
    if (userCategories || !_isFunction(sortingMethodOption) && groupsData.argumentType === STRING && !options._skipArgumentSorting) {
        sortFunction = getSortByCategories(groupsData.categories)
    } else if (true === sortingMethodOption && groupsData.argumentType !== STRING) {
        sortFunction = sortByArgument;
        reSortCategories = isDiscrete
    }
    uniqueArgumentFields.forEach((function(field) {
        dataByArguments[field] = sortFunction(data, field)
    }));
    if (reSortCategories) {
        groupsData.categories = groupsData.categories.sort(sort)
    }
    return dataByArguments
}

function checkItemExistence(collection, item) {
    return -1 === collection.map((function(collectionItem) {
        return collectionItem.valueOf()
    })).indexOf(item.valueOf())
}

function getCategories(data, uniqueArgumentFields, userCategories) {
    const categories = userCategories ? userCategories.slice() : [];
    uniqueArgumentFields.forEach((function(field) {
        data.forEach((function(item) {
            const dataItem = item[field];
            _isDefined(dataItem) && checkItemExistence(categories, dataItem) && categories.push(dataItem)
        }))
    }));
    return categories
}

function checkArgumentTypeOfGroup(series, cell, groupsData) {
    series.forEach((function(currentSeries) {
        groupsData.argumentType = getType(cell[currentSeries.getArgumentField()], groupsData.argumentType)
    }));
    return groupsData.argumentType
}

function checkType(data, groupsData, checkTypeForAllData) {
    const groupsWithUndefinedValueType = [];
    const groupsWithUndefinedArgumentType = [];
    const argumentTypeGroup = groupsData.argumentOptions && axisTypeParser(groupsData.argumentOptions.argumentType);
    let groupsIndexes;
    groupsData.groups.forEach((function(group) {
        if (!group.series.length) {
            return
        }
        const valueTypeGroup = group.valueOptions && axisTypeParser(group.valueOptions.valueType);
        group.valueType = valueTypeGroup;
        groupsData.argumentType = argumentTypeGroup;
        !valueTypeGroup && groupsWithUndefinedValueType.push(group);
        !argumentTypeGroup && groupsWithUndefinedArgumentType.push(group)
    }));
    if (groupsWithUndefinedValueType.length || groupsWithUndefinedArgumentType.length) {
        groupsIndexes = groupsWithUndefinedValueType.map((function(_, index) {
            return index
        }));
        data.some((function(cell) {
            let defineArg;
            groupsWithUndefinedValueType.forEach((function(group, groupIndex) {
                if (checkValueTypeOfGroup(group, cell) && groupsIndexes.indexOf(groupIndex) >= 0) {
                    groupsIndexes.splice(groupIndex, 1)
                }
            }));
            if (!defineArg) {
                groupsWithUndefinedArgumentType.forEach((function(group) {
                    defineArg = checkArgumentTypeOfGroup(group.series, cell, groupsData)
                }))
            }
            if (!checkTypeForAllData && defineArg && 0 === groupsIndexes.length) {
                return true
            }
        }))
    }
}

function checkAxisType(groupsData, incidentOccurred) {
    const argumentOptions = groupsData.argumentOptions || {};
    const userArgumentCategories = argumentOptions && argumentOptions.categories || [];
    const argumentAxisType = correctAxisType(groupsData.argumentType, argumentOptions.type, !!userArgumentCategories.length, incidentOccurred);
    groupsData.groups.forEach((function(group) {
        const valueOptions = group.valueOptions || {};
        const valueCategories = valueOptions.categories || [];
        const valueAxisType = correctAxisType(group.valueType, valueOptions.type, !!valueCategories.length, incidentOccurred);
        group.series.forEach((function(series) {
            const optionsSeries = {};
            optionsSeries.argumentAxisType = argumentAxisType;
            optionsSeries.valueAxisType = valueAxisType;
            groupsData.argumentAxisType = groupsData.argumentAxisType || optionsSeries.argumentAxisType;
            group.valueAxisType = group.valueAxisType || optionsSeries.valueAxisType;
            optionsSeries.argumentType = groupsData.argumentType;
            optionsSeries.valueType = group.valueType;
            optionsSeries.showZero = valueOptions.showZero;
            series.updateDataType(optionsSeries)
        }));
        group.valueAxisType = group.valueAxisType || valueAxisType;
        if (group.valueAxis) {
            group.valueAxis.setTypes(group.valueAxisType, group.valueType, VALUE_TYPE);
            group.valueAxis.validate()
        }
    }));
    groupsData.argumentAxisType = groupsData.argumentAxisType || argumentAxisType;
    if (groupsData.argumentAxes) {
        groupsData.argumentAxes.forEach((function(axis) {
            axis.setTypes(groupsData.argumentAxisType, groupsData.argumentType, ARGUMENT_TYPE);
            axis.validate()
        }))
    }
}

function verifyData(source, incidentOccurred) {
    const data = [];
    const sourceIsDefined = _isDefined(source);
    let hasError = sourceIsDefined && !_isArray(source);
    let i;
    let ii;
    let k;
    let item;
    if (sourceIsDefined && !hasError) {
        for (i = 0, ii = source.length, k = 0; i < ii; ++i) {
            item = source[i];
            if (_isObject(item)) {
                data[k++] = item
            } else if (item) {
                hasError = true
            }
        }
    }
    if (hasError) {
        incidentOccurred("E2001")
    }
    return data
}
export function validateData(data, groupsData, incidentOccurred, options) {
    data = verifyData(data, incidentOccurred);
    groupsData.argumentType = groupsData.argumentAxisType = null;
    processGroups(groupsData.groups);
    resetArgumentAxes(groupsData.argumentAxes);
    checkType(data, groupsData, options.checkTypeForAllData);
    checkAxisType(groupsData, incidentOccurred);
    if (options.convertToAxisDataType) {
        data = parse(data, prepareParsers(groupsData, incidentOccurred))
    }
    groupPieData(data, groupsData);
    const dataByArgumentFields = sortData(data, groupsData, options, getUniqueArgumentFields(groupsData));
    return dataByArgumentFields
}
