/**
 * DevExtreme (esm/__internal/grids/grid_core/m_utils.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 _extends from "@babel/runtime/helpers/esm/extends";
import $ from "../../../core/renderer";
import {
    equalByValue
} from "../../../core/utils/common";
import {
    toComparable
} from "../../../core/utils/data";
import {
    Deferred,
    when
} from "../../../core/utils/deferred";
import {
    extend
} from "../../../core/utils/extend";
import {
    each
} from "../../../core/utils/iterator";
import {
    getBoundingRect
} from "../../../core/utils/position";
import {
    getHeight
} from "../../../core/utils/size";
import {
    format
} from "../../../core/utils/string";
import {
    isDefined,
    isFunction,
    isString
} from "../../../core/utils/type";
import variableWrapper from "../../../core/utils/variable_wrapper";
import {
    getWindow
} from "../../../core/utils/window";
import {
    DataSource
} from "../../../data/data_source/data_source";
import {
    normalizeDataSourceOptions
} from "../../../data/data_source/utils";
import {
    normalizeSortingInfo as normalizeSortingInfoUtility
} from "../../../data/utils";
import eventsEngine from "../../../events/core/events_engine";
import formatHelper from "../../../format_helper";
import LoadPanel from "../../../ui/load_panel";
import sharedFiltering from "../../../ui/shared/filtering";
const DATAGRID_SELECTION_DISABLED_CLASS = "dx-selection-disabled";
const DATAGRID_GROUP_OPENED_CLASS = "dx-datagrid-group-opened";
const DATAGRID_GROUP_CLOSED_CLASS = "dx-datagrid-group-closed";
const DATAGRID_EXPAND_CLASS = "dx-datagrid-expand";
const NO_DATA_CLASS = "nodata";
const SCROLLING_MODE_INFINITE = "infinite";
const SCROLLING_MODE_VIRTUAL = "virtual";
const LEGACY_SCROLLING_MODE = "scrolling.legacyMode";
const SCROLLING_MODE_OPTION = "scrolling.mode";
const ROW_RENDERING_MODE_OPTION = "scrolling.rowRenderingMode";
const DATE_INTERVAL_SELECTORS = {
    year: value => value && value.getFullYear(),
    month: value => value && value.getMonth() + 1,
    day: value => value && value.getDate(),
    quarter: value => value && Math.floor(value.getMonth() / 3) + 1,
    hour: value => value && value.getHours(),
    minute: value => value && value.getMinutes(),
    second: value => value && value.getSeconds()
};
const getIntervalSelector = function() {
    const data = arguments[1];
    const value = this.calculateCellValue(data);
    if (!isDefined(value)) {
        return null
    }
    if (isDateType(this.dataType)) {
        const nameIntervalSelector = arguments[0];
        return DATE_INTERVAL_SELECTORS[nameIntervalSelector](value)
    }
    if ("number" === this.dataType) {
        const groupInterval = arguments[0];
        return Math.floor(Number(value) / groupInterval) * groupInterval
    }
};
const equalSelectors = function(selector1, selector2) {
    if (isFunction(selector1) && isFunction(selector2)) {
        if (selector1.originalCallback && selector2.originalCallback) {
            return selector1.originalCallback === selector2.originalCallback && selector1.columnIndex === selector2.columnIndex
        }
    }
    return selector1 === selector2
};

function isDateType(dataType) {
    return "date" === dataType || "datetime" === dataType
}
const setEmptyText = function($container) {
    $container.get(0).textContent = "\xa0"
};
const normalizeSortingInfo = function(sort) {
    sort = sort || [];
    const result = normalizeSortingInfoUtility(sort);
    for (let i = 0; i < sort.length; i++) {
        if (sort && sort[i] && void 0 !== sort[i].isExpanded) {
            result[i].isExpanded = sort[i].isExpanded
        }
        if (sort && sort[i] && void 0 !== sort[i].groupInterval) {
            result[i].groupInterval = sort[i].groupInterval
        }
    }
    return result
};
const formatValue = function(value, options) {
    const valueText = formatHelper.format(value, options.format) || value && value.toString() || "";
    const formatObject = {
        value: value,
        valueText: options.getDisplayFormat ? options.getDisplayFormat(valueText) : valueText,
        target: options.target || "row",
        groupInterval: options.groupInterval
    };
    return options.customizeText ? options.customizeText.call(options, formatObject) : formatObject.valueText
};
const getSummaryText = function(summaryItem, summaryTexts) {
    const displayFormat = summaryItem.displayFormat || summaryItem.columnCaption && summaryTexts[`${summaryItem.summaryType}OtherColumn`] || summaryTexts[summaryItem.summaryType];
    return formatValue(summaryItem.value, {
        format: summaryItem.valueFormat,
        getDisplayFormat: valueText => displayFormat ? format(displayFormat, valueText, summaryItem.columnCaption) : valueText,
        customizeText: summaryItem.customizeText
    })
};
const getWidgetInstance = function($element) {
    const editorData = $element.data && $element.data();
    const dxComponents = editorData && editorData.dxComponents;
    const widgetName = dxComponents && dxComponents[0];
    return widgetName && editorData[widgetName]
};
const equalFilterParameters = function(filter1, filter2) {
    if (Array.isArray(filter1) && Array.isArray(filter2)) {
        if (filter1.length !== filter2.length) {
            return false
        }
        for (let i = 0; i < filter1.length; i++) {
            if (!equalFilterParameters(filter1[i], filter2[i])) {
                return false
            }
        }
        return true
    }
    if (isFunction(filter1) && filter1.columnIndex >= 0 && isFunction(filter2) && filter2.columnIndex >= 0) {
        return filter1.columnIndex === filter2.columnIndex && toComparable(filter1.filterValue) === toComparable(filter2.filterValue) && toComparable(filter1.selectedFilterOperation) === toComparable(filter2.selectedFilterOperation)
    }
    return toComparable(filter1) == toComparable(filter2)
};

function normalizeGroupingLoadOptions(group) {
    if (!Array.isArray(group)) {
        group = [group]
    }
    return group.map(((item, i) => {
        if (isString(item)) {
            return {
                selector: item,
                isExpanded: i < group.length - 1
            }
        }
        return item
    }))
}
export default {
    renderNoDataText($element) {
        const that = this;
        $element = $element || this.element();
        if (!$element) {
            return
        }
        const noDataClass = that.addWidgetPrefix("nodata");
        let noDataElement = $element.find(`.${noDataClass}`).last();
        const isVisible = this._dataController.isEmpty();
        const isLoading = this._dataController.isLoading();
        if (!noDataElement.length) {
            noDataElement = $("<span>").addClass(noDataClass)
        }
        if (!noDataElement.parent().is($element)) {
            noDataElement.appendTo($element)
        }
        if (isVisible && !isLoading) {
            noDataElement.removeClass("dx-hidden").text(that._getNoDataText())
        } else {
            noDataElement.addClass("dx-hidden")
        }
    },
    renderLoadPanel($element, $container, isLocalStore) {
        const that = this;
        let loadPanelOptions;
        that._loadPanel && that._loadPanel.$element().remove();
        loadPanelOptions = that.option("loadPanel");
        if (loadPanelOptions && ("auto" === loadPanelOptions.enabled ? !isLocalStore : loadPanelOptions.enabled)) {
            loadPanelOptions = extend({
                shading: false,
                message: loadPanelOptions.text,
                container: $container
            }, loadPanelOptions);
            that._loadPanel = that._createComponent($("<div>").appendTo($container), LoadPanel, loadPanelOptions)
        } else {
            that._loadPanel = null
        }
    },
    calculateLoadPanelPosition($element) {
        const $window = $(getWindow());
        if (getHeight($element) > getHeight($window)) {
            return {
                of: $window,
                boundary: $element,
                collision: "fit"
            }
        }
        return {
            of: $element
        }
    },
    getIndexByKey(key, items, keyName) {
        let index = -1;
        if (void 0 !== key && Array.isArray(items)) {
            keyName = arguments.length <= 2 ? "key" : keyName;
            for (let i = 0; i < items.length; i++) {
                const item = isDefined(keyName) ? items[i][keyName] : items[i];
                if (equalByValue(key, item)) {
                    index = i;
                    break
                }
            }
        }
        return index
    },
    combineFilters(filters, operation) {
        let resultFilter = [];
        operation = operation || "and";
        for (let i = 0; i < filters.length; i++) {
            var _filters$i;
            if (!filters[i]) {
                continue
            }
            if (1 === (null === (_filters$i = filters[i]) || void 0 === _filters$i ? void 0 : _filters$i.length) && "!" === filters[i][0]) {
                if ("and" === operation) {
                    return ["!"]
                }
                if ("or" === operation) {
                    continue
                }
            }
            if (resultFilter.length) {
                resultFilter.push(operation)
            }
            resultFilter.push(filters[i])
        }
        if (1 === resultFilter.length) {
            resultFilter = resultFilter[0]
        }
        if (resultFilter.length) {
            return resultFilter
        }
        return
    },
    checkChanges(changes, changeNames) {
        let changesWithChangeNamesCount = 0;
        for (let i = 0; i < changeNames.length; i++) {
            if (changes[changeNames[i]]) {
                changesWithChangeNamesCount++
            }
        }
        return changes.length && changes.length === changesWithChangeNamesCount
    },
    equalFilterParameters: equalFilterParameters,
    proxyMethod(instance, methodName, defaultResult) {
        if (!instance[methodName]) {
            instance[methodName] = function() {
                const dataSource = this._dataSource;
                return dataSource ? dataSource[methodName].apply(dataSource, arguments) : defaultResult
            }
        }
    },
    formatValue: formatValue,
    getFormatOptionsByColumn: (column, target) => ({
        format: column.format,
        getDisplayFormat: column.getDisplayFormat,
        customizeText: column.customizeText,
        target: target,
        trueText: column.trueText,
        falseText: column.falseText
    }),
    getDisplayValue(column, value, data, rowType) {
        if (column.displayValueMap && void 0 !== column.displayValueMap[value]) {
            return column.displayValueMap[value]
        }
        if (column.calculateDisplayValue && data && "group" !== rowType) {
            return column.calculateDisplayValue(data)
        }
        if (column.lookup && !("group" === rowType && (column.calculateGroupValue || column.calculateDisplayValue))) {
            return column.lookup.calculateCellValue(value)
        }
        return value
    },
    getGroupRowSummaryText(summaryItems, summaryTexts) {
        let result = "(";
        for (let i = 0; i < summaryItems.length; i++) {
            const summaryItem = summaryItems[i];
            result += (i > 0 ? ", " : "") + getSummaryText(summaryItem, summaryTexts)
        }
        return result + ")"
    },
    getSummaryText: getSummaryText,
    normalizeSortingInfo: normalizeSortingInfo,
    getFormatByDataType(dataType) {
        switch (dataType) {
            case "date":
                return "shortDate";
            case "datetime":
                return "shortDateShortTime";
            default:
                return
        }
    },
    getHeaderFilterGroupParameters(column, remoteGrouping) {
        let result = [];
        const dataField = column.dataField || column.name;
        const groupInterval = sharedFiltering.getGroupInterval(column);
        if (groupInterval) {
            each(groupInterval, ((index, interval) => {
                result.push(remoteGrouping ? {
                    selector: dataField,
                    groupInterval: interval,
                    isExpanded: index < groupInterval.length - 1
                } : getIntervalSelector.bind(column, interval))
            }));
            return result
        }
        if (remoteGrouping) {
            result = [{
                selector: dataField,
                isExpanded: false
            }]
        } else {
            result = function(data) {
                let result = column.calculateCellValue(data);
                if (void 0 === result || "" === result) {
                    result = null
                }
                return result
            };
            if (column.sortingMethod) {
                result = [{
                    selector: result,
                    compare: column.sortingMethod.bind(column)
                }]
            }
        }
        return result
    },
    equalSortParameters(sortParameters1, sortParameters2, ignoreIsExpanded) {
        sortParameters1 = normalizeSortingInfo(sortParameters1);
        sortParameters2 = normalizeSortingInfo(sortParameters2);
        if (Array.isArray(sortParameters1) && Array.isArray(sortParameters2)) {
            if (sortParameters1.length !== sortParameters2.length) {
                return false
            }
            for (let i = 0; i < sortParameters1.length; i++) {
                if (!equalSelectors(sortParameters1[i].selector, sortParameters2[i].selector) || sortParameters1[i].desc !== sortParameters2[i].desc || sortParameters1[i].groupInterval !== sortParameters2[i].groupInterval || !ignoreIsExpanded && Boolean(sortParameters1[i].isExpanded) !== Boolean(sortParameters2[i].isExpanded)) {
                    return false
                }
            }
            return true
        }
        return (!sortParameters1 || !sortParameters1.length) === (!sortParameters2 || !sortParameters2.length)
    },
    getPointsByColumns(items, pointCreated, isVertical, startColumnIndex) {
        const cellsLength = items.length;
        let notCreatePoint = false;
        let item;
        let offset;
        let columnIndex = startColumnIndex || 0;
        const result = [];
        let rtlEnabled;
        for (let i = 0; i <= cellsLength; i++) {
            if (i < cellsLength) {
                item = items.eq(i);
                offset = item.offset();
                rtlEnabled = "rtl" === item.css("direction")
            }
            const point = {
                index: columnIndex,
                x: offset ? offset.left + (!isVertical && rtlEnabled ^ i === cellsLength ? getBoundingRect(item[0]).width : 0) : 0,
                y: offset ? offset.top + (isVertical && i === cellsLength ? getBoundingRect(item[0]).height : 0) : 0,
                columnIndex: columnIndex
            };
            if (!isVertical && i > 0) {
                const prevItemOffset = items.eq(i - 1).offset();
                if (prevItemOffset.top < point.y) {
                    point.y = prevItemOffset.top
                }
            }
            if (pointCreated) {
                notCreatePoint = pointCreated(point)
            }
            if (!notCreatePoint) {
                result.push(point)
            }
            columnIndex++
        }
        return result
    },
    getExpandCellTemplate: () => ({
        allowRenderToDetachedContainer: true,
        render(container, options) {
            const $container = $(container);
            if (isDefined(options.value) && !(options.data && options.data.isContinuation) && !options.row.isNewRow) {
                const rowsView = options.component.getView("rowsView");
                $container.addClass("dx-datagrid-expand").addClass("dx-selection-disabled");
                $("<div>").addClass(options.value ? "dx-datagrid-group-opened" : "dx-datagrid-group-closed").appendTo($container);
                rowsView.setAria("label", options.value ? rowsView.localize("dxDataGrid-ariaCollapse") : rowsView.localize("dxDataGrid-ariaExpand"), $container)
            } else {
                setEmptyText($container)
            }
        }
    }),
    setEmptyText: setEmptyText,
    isDateType: isDateType,
    getSelectionRange(focusedElement) {
        try {
            if (focusedElement) {
                return {
                    selectionStart: focusedElement.selectionStart,
                    selectionEnd: focusedElement.selectionEnd
                }
            }
        } catch (e) {}
        return {}
    },
    setSelectionRange(focusedElement, selectionRange) {
        try {
            if (focusedElement && focusedElement.setSelectionRange) {
                focusedElement.setSelectionRange(selectionRange.selectionStart, selectionRange.selectionEnd)
            }
        } catch (e) {}
    },
    focusAndSelectElement(component, $element) {
        const isFocused = $element.is(":focus");
        eventsEngine.trigger($element, "focus");
        const isSelectTextOnEditingStart = component.option("editing.selectTextOnEditStart");
        const element = $element.get(0);
        if (!isFocused && isSelectTextOnEditingStart && $element.is(".dx-texteditor-input") && !$element.is("[readonly]")) {
            const editor = getWidgetInstance($element.closest(".dx-texteditor"));
            when(editor && editor._loadItemDeferred).done((() => {
                element.select()
            }))
        }
    },
    getWidgetInstance: getWidgetInstance,
    getLastResizableColumnIndex(columns, resultWidths) {
        const hasResizableColumns = columns.some((column => column && !column.command && !column.fixed && false !== column.allowResizing));
        let lastColumnIndex;
        for (lastColumnIndex = columns.length - 1; columns[lastColumnIndex]; lastColumnIndex--) {
            const column = columns[lastColumnIndex];
            const width = resultWidths && resultWidths[lastColumnIndex];
            const allowResizing = !hasResizableColumns || false !== column.allowResizing;
            if (!column.command && !column.fixed && "adaptiveHidden" !== width && allowResizing) {
                break
            }
        }
        return lastColumnIndex
    },
    isElementInCurrentGrid(controller, $element) {
        if ($element && $element.length) {
            const $grid = $element.closest(`.${controller.getWidgetContainerClass()}`).parent();
            return $grid.is(controller.component.$element())
        }
        return false
    },
    isVirtualRowRendering(that) {
        const rowRenderingMode = that.option(ROW_RENDERING_MODE_OPTION);
        const isVirtualMode = "virtual" === that.option("scrolling.mode");
        const isAppendMode = "infinite" === that.option("scrolling.mode");
        if (false === that.option(LEGACY_SCROLLING_MODE) && (isVirtualMode || isAppendMode)) {
            return true
        }
        return "virtual" === rowRenderingMode
    },
    getPixelRatio: window => window.devicePixelRatio || 1,
    getContentHeightLimit(browser) {
        if (browser.mozilla) {
            return 8e6
        }
        return 15e6 / this.getPixelRatio(getWindow())
    },
    normalizeLookupDataSource(lookup) {
        let lookupDataSourceOptions;
        if (lookup.items) {
            lookupDataSourceOptions = lookup.items
        } else {
            lookupDataSourceOptions = lookup.dataSource;
            if (isFunction(lookupDataSourceOptions) && !variableWrapper.isWrapped(lookupDataSourceOptions)) {
                lookupDataSourceOptions = lookupDataSourceOptions({})
            }
        }
        return normalizeDataSourceOptions(lookupDataSourceOptions)
    },
    getWrappedLookupDataSource(column, dataSource, filter) {
        if (!dataSource) {
            return []
        }
        const lookupDataSourceOptions = this.normalizeLookupDataSource(column.lookup);
        if (column.calculateCellValue !== column.defaultCalculateCellValue) {
            return lookupDataSourceOptions
        }
        const hasGroupPaging = dataSource.remoteOperations().groupPaging;
        const hasLookupOptimization = column.displayField && isString(column.displayField);
        let cachedUniqueRelevantItems;
        let previousTake;
        let previousSkip;
        const sliceItems = (items, loadOptions) => {
            const start = loadOptions.skip ?? 0;
            const end = loadOptions.take ? start + loadOptions.take : items.length;
            return items.slice(start, end)
        };
        const lookupDataSource = _extends({}, lookupDataSourceOptions, {
            __dataGridSourceFilter: filter,
            load: loadOptions => {
                const d = new Deferred;
                (loadOptions => {
                    const group = normalizeGroupingLoadOptions(hasLookupOptimization ? [column.dataField, column.displayField] : column.dataField);
                    const d = new Deferred;
                    const canUseCache = cachedUniqueRelevantItems && (!hasGroupPaging || loadOptions.skip === previousSkip && loadOptions.take === previousTake);
                    if (canUseCache) {
                        d.resolve(sliceItems(cachedUniqueRelevantItems, loadOptions))
                    } else {
                        previousSkip = loadOptions.skip;
                        previousTake = loadOptions.take;
                        dataSource.load({
                            filter: filter,
                            group: group,
                            take: hasGroupPaging ? loadOptions.take : void 0,
                            skip: hasGroupPaging ? loadOptions.skip : void 0
                        }).done((items => {
                            cachedUniqueRelevantItems = items;
                            d.resolve(hasGroupPaging ? items : sliceItems(items, loadOptions))
                        })).fail(d.fail)
                    }
                    return d
                })(loadOptions).done((items => {
                    if (0 === items.length) {
                        d.resolve([]);
                        return
                    }
                    const filter = this.combineFilters(items.flatMap((data => data.key)).map((key => [column.lookup.valueExpr, key])), "or");
                    const newDataSource = new DataSource(_extends({}, lookupDataSourceOptions, loadOptions, {
                        filter: this.combineFilters([filter, loadOptions.filter], "and"),
                        paginate: false
                    }));
                    newDataSource.load().done(d.resolve).fail(d.fail)
                })).fail(d.fail);
                return d
            },
            key: column.lookup.valueExpr,
            byKey(key) {
                const d = Deferred();
                this.load({
                    filter: [column.lookup.valueExpr, "=", key]
                }).done((arr => {
                    d.resolve(arr[0])
                }));
                return d.promise()
            }
        });
        return lookupDataSource
    },
    logHeaderFilterDeprecatedWarningIfNeed(component) {
        const logWarning = component._logDeprecatedOptionWarning.bind(component);
        if (isDefined(component.option("headerFilter.allowSearch"))) {
            logWarning("headerFilter.allowSearch", {
                since: "23.1",
                alias: "headerFilter.search.enabled"
            })
        }
        if (isDefined(component.option("headerFilter.searchTimeout"))) {
            logWarning("headerFilter.searchTimeout", {
                since: "23.1",
                alias: "headerFilter.search.timeout"
            })
        }
        const specificName = "dxPivotGrid" === component.NAME ? "dataSource.fields" : "columns";
        const columns = component.option(specificName);
        if (!Array.isArray(columns)) {
            return
        }
        const logSpecificDeprecatedWarningIfNeed = columns => {
            columns.forEach((column => {
                var _column$columns;
                const headerFilter = column.headerFilter || {};
                if (isDefined(headerFilter.allowSearch)) {
                    logWarning(`${specificName}[].headerFilter.allowSearch`, {
                        since: "23.1",
                        alias: `${specificName}[].headerFilter.search.enabled`
                    })
                }
                if (isDefined(headerFilter.searchMode)) {
                    logWarning(`${specificName}[].headerFilter.searchMode`, {
                        since: "23.1",
                        alias: `${specificName}[].headerFilter.search.mode`
                    })
                }
                if (null !== (_column$columns = column.columns) && void 0 !== _column$columns && _column$columns.length) {
                    logSpecificDeprecatedWarningIfNeed(column.columns)
                }
            }))
        };
        logSpecificDeprecatedWarningIfNeed(columns)
    }
};
