import merge from 'lodash/merge';
import { FilterWrapperFactory } from './FilterWrapper';
import { FloatingFilterWrapperFactory } from './FloatingFilterWrapper';
import { convertAdaptableStyleToCSS, getVariableColor, normalizeStyleForAgGrid, } from '../Utilities/Helpers/StyleHelper';
import StringExtensions from '../Utilities/Extensions/StringExtensions';
import { ACTION_COLUMN_TYPE, CALCULATED_COLUMN_TYPE, FDC3_COLUMN_TYPE, FREE_TEXT_COLUMN_TYPE, } from '../AdaptableState/Common/AdaptableColumn';
import tinycolor from 'tinycolor2';
import UIHelper from '../View/UIHelper';
import { getPercentBarRendererForColumn } from './cellRenderers/PercentBarRenderer';
import { getBadgeRendererForColumn } from './cellRenderers/BadgeRenderer';
import Helper from '../Utilities/Helpers/Helper';
import { AdaptableNumberEditor, AdaptableReactNumberEditor } from './editors/AdaptableNumberEditor';
import { AdaptableDateEditor, AdaptableReactDateEditor } from './editors/AdaptableDateEditor';
import { AgGridExportAdapter } from './AgGridExportAdapter';
import { AdaptableHelper, } from '../Utilities/Helpers/AdaptableHelper';
export function getEditorForColumnDataType(columnDataType, variant) {
    if (columnDataType === 'number') {
        return variant === 'react' ? AdaptableReactNumberEditor : AdaptableNumberEditor;
    }
    if (columnDataType === 'date' || columnDataType === 'dateString') {
        return variant === 'react' ? AdaptableReactDateEditor : AdaptableDateEditor;
    }
}
export class AgGridColumnAdapter {
    constructor(adaptableInstance) {
        this.adaptableInstance = adaptableInstance;
        this.colDefPropertyCache = new Map();
    }
    getVariant() {
        return this.adaptableInstance.variant;
    }
    destroy() {
        this.adaptableInstance = null;
        this.colDefPropertyCache.clear();
        this.colDefPropertyCache = null;
    }
    get adaptableApi() {
        return this.adaptableInstance.api;
    }
    get adaptableOptions() {
        return this.adaptableInstance.adaptableOptions;
    }
    get agGridApi() {
        return this.adaptableApi.agGridApi;
    }
    setColDefProperty(col, propertyName, propertyGetter) {
        const colId = col.getColId();
        const colDef = col.getColDef();
        const colSetupInfo = {
            col,
            colDef,
            colId,
        };
        const userKey = `user.${colId}.${propertyName}`;
        const adaptableKey = `adaptable.${colId}.${propertyName}`;
        const value = colDef[propertyName];
        const isUserDefined = value !== this.colDefPropertyCache.get(adaptableKey);
        if (isUserDefined) {
            this.colDefPropertyCache.set(userKey, value);
        }
        const userValue = this.colDefPropertyCache.get(userKey);
        const adaptableValue = propertyGetter(userValue);
        if (adaptableValue != null) {
            this.colDefPropertyCache.set(adaptableKey, adaptableValue);
        }
        let theValue = adaptableValue ?? userValue;
        this.adaptableInstance.forPlugins((plugin) => {
            if (plugin.interceptSetupColumnProperty) {
                theValue = plugin.interceptSetupColumnProperty(colSetupInfo, propertyName, theValue, this.adaptableApi);
            }
        });
        if (propertyName === 'aggFunc') {
            if (colDef[propertyName] !== (theValue ?? null)) {
                this.agGridApi?.setColumnAggFunc(colId, theValue ?? null);
            }
        }
        if (theValue === undefined && colDef[propertyName] === undefined) {
            // already undefined, so don't set an own property to the same undefined value
            return;
        }
        colDef[propertyName] = theValue;
    }
    getUserColDefProperty(columnId, propertyName) {
        const userKey = `user.${columnId}.${propertyName}`;
        return this.colDefPropertyCache.get(userKey);
    }
    shouldSkipColumn(colId) {
        /**
         * This skips columns like `ag-Grid-SelectionColumn` and possibly other columns
         * that ag grid will implement in the future
         *
         * BUT DOES NOT SKIP GROUP COLUMNS!!!
         */
        return colId.startsWith('ag-Grid-') && !this.adaptableApi.columnApi.isAutoRowGroupColumn(colId);
    }
    setupColumns() {
        const pivotMode = this.agGridApi.isPivotMode();
        let cols = pivotMode
            ? // for pivot mode, we take only the initial columns
                this.agGridApi.getColumns()
            : // but for non-pivot mode, we ask for all columns
                // which also includes those that are generated for row grouping
                this.agGridApi.getAllGridColumns();
        if (pivotMode) {
            const pivotResultColumns = this.agGridApi.getPivotResultColumns() || [];
            if (pivotResultColumns.length) {
                cols = [...cols, ...pivotResultColumns];
            }
        }
        // this needs to be here, before the other setup below
        // so the setup methods below reference the correct columns in adaptable store
        cols.forEach((col) => {
            const colDef = col.getColDef();
            const colId = col.getColId();
            if (this.shouldSkipColumn(colId)) {
                return;
            }
            const abColumn = this.adaptableApi.columnApi.getColumnWithColumnId(colId);
            const colSetupInfo = {
                col,
                colDef,
                colId,
                abColumn,
            };
            this.setupColumnCellRenderer(colSetupInfo);
            this.setupColumnCellStyle(colSetupInfo);
            this.setupColumnCellClass(colSetupInfo);
            this.setupColumnTooltipValueGetter(colSetupInfo);
            this.setupColumnValueGetter(colSetupInfo);
            this.setupColumnFilter(colSetupInfo);
            this.setupColumnFloatingFilter(colSetupInfo);
            this.setupColumnValueFormatter(colSetupInfo);
            this.setupColumnEditable(colSetupInfo);
            this.setupColumnValueSetter(colSetupInfo);
            this.setupColumnComparator(colSetupInfo);
            this.setupColumnCellEditor(colSetupInfo);
            this.setupColumnHeader(colSetupInfo);
            this.setupColumnQuickFilterText(colSetupInfo);
            this.setupColumnAllowedAggFuncs(colSetupInfo);
            this.setupColumnType(colSetupInfo);
            this.setupColumnCellDataType(colSetupInfo);
        });
    }
    setupColumnValueGetter({ col }) {
        // need this here if we want plugins to intercept
        this.setColDefProperty(col, 'valueGetter', (userValue) => {
            return userValue;
        });
    }
    setupColumnCellClass({ col, colId, abColumn }) {
        this.setColDefProperty(col, 'cellClass', (userCellClass) => {
            const formatColumns = this.adaptableApi.formatColumnApi.internalApi.getFormatColumnWithStyleClassNameForColumn(abColumn);
            const quickSearchStyleClassName = this.adaptableApi.quickSearchApi.getQuickSearchStyle().ClassName;
            const hasQuickSearchStyleClassName = StringExtensions.IsNotNullOrEmpty(quickSearchStyleClassName);
            const cellClass = (params) => {
                const gridCell = this.adaptableApi.gridApi.getGridCellFromRowNode(params.node, abColumn.columnId);
                if (!gridCell.column) {
                    return null;
                }
                // if a VisualExcel report format export is in progress, we are interested only in the Excel Style Class
                if (this.adaptableApi.exportApi.internalApi.isVisualDataExportInProgress()) {
                    const userDefinedCellClass = typeof userCellClass === 'function' ? userCellClass(params) : userCellClass;
                    const cellClassKey = AgGridExportAdapter.getExcelClassNameForCell(colId, gridCell.primaryKeyValue, userDefinedCellClass);
                    return this.adaptableInstance.agGridExportAdapter.getExcelStyleIdForCellClassKey(cellClassKey);
                }
                const isQuickSearchActive = hasQuickSearchStyleClassName && this.isQuickSearchActive(gridCell, params);
                const editableClassName = this.getEditableCellClass(gridCell, params);
                const readonlyClassName = this.getReadonlyCellClass(gridCell, params);
                const editedClassName = this.getEditedCellClass(gridCell, params);
                const highlightAlertClassName = this.getAlertCellClass(gridCell, params);
                const flashingClassName = this.getFlashingCellClass(gridCell, params);
                const styledColumn = this.adaptableApi.styledColumnApi.getStyledColumnForColumnId(colId);
                const hasStyledColumn = !!styledColumn && !styledColumn.IsSuspended;
                const noteClassName = this.getNoteCellClassName(gridCell, params);
                const commentsClassName = this.getCommentCellClassName(gridCell, params);
                const returnValue = [
                    typeof userCellClass === 'function' ? userCellClass(params) : userCellClass,
                    !hasStyledColumn && formatColumns.length
                        ? this.getFormatColumnCellClass(formatColumns, abColumn, params)
                        : null,
                    isQuickSearchActive ? quickSearchStyleClassName : null,
                    editableClassName,
                    readonlyClassName,
                    editedClassName,
                    highlightAlertClassName,
                    flashingClassName,
                    noteClassName,
                    commentsClassName,
                ]
                    // we flatten the array because some rules ('userCellClass' etc)  might return a string[]
                    .flat()
                    .filter((x) => !!x);
                const result = returnValue.length ? returnValue : undefined;
                return result;
            };
            return cellClass;
        });
    }
    setupColumnCellStyle({ col, colId, abColumn }) {
        this.setColDefProperty(col, 'cellStyle', (userCellStyle) => {
            const quickSearchStyle = this.getQuickSearchCellStyle();
            const hasQuickSearchStyle = quickSearchStyle != undefined;
            const cellStyle = (params) => {
                const gridCell = this.adaptableApi.gridApi.getGridCellFromRowNode(params.node, abColumn.columnId);
                if (!gridCell || !gridCell.column) {
                    return {};
                }
                const isQuickSearchActive = hasQuickSearchStyle && this.isQuickSearchActive(gridCell, params);
                const userDefined = typeof userCellStyle === 'function' ? userCellStyle(params) : userCellStyle;
                const result = {
                    ...userDefined,
                    ...this.getReadOnlyCellStyle(gridCell, params),
                    ...this.getEditableCellStyle(gridCell, params),
                    ...this.getEditedCellStyle(gridCell, params),
                    ...this.getFormatColumnAndStyledColumnCellStyle(gridCell, params),
                    ...(isQuickSearchActive ? quickSearchStyle : {}),
                    ...this.getAlertCellStyle(gridCell, params),
                    ...this.getFlashingCellStyle(gridCell, params),
                    ...this.getCellHighlightStyle(gridCell, params),
                };
                return normalizeStyleForAgGrid(result);
            };
            return cellStyle;
        });
    }
    setupColumnCellEditor({ colId, col, colDef, abColumn }) {
        const shouldShowSelectCellEditor = this.adaptableApi.userInterfaceApi.internalApi.shouldShowSelectCellEditor(abColumn);
        const hasRichSelectCellEditor = this.adaptableInstance.agGridAdapter.isAgGridModuleRegistered('RichSelect');
        this.setColDefProperty(col, 'cellEditor', () => {
            if (shouldShowSelectCellEditor) {
                return hasRichSelectCellEditor ? 'agRichSelectCellEditor' : 'agSelectCellEditor';
            }
            else {
                if (colDef.cellEditor) {
                    return colDef.cellEditor;
                }
                const cellDataTypeEditor = getEditorForColumnDataType(abColumn.dataType, this.getVariant());
                return cellDataTypeEditor;
            }
        });
        this.setColDefProperty(col, 'cellEditorParams', (params) => {
            if (shouldShowSelectCellEditor) {
                return (params) => {
                    const gridCell = this.adaptableApi.gridApi.getGridCellFromRowNode(params?.node, colId);
                    const options = this.adaptableApi.gridApi.internalApi.getDistinctEditDisplayValuesForColumn({
                        columnId: colId,
                        gridCell,
                        currentSearchValue: '',
                    });
                    const valueToLabelMap = new Map();
                    const values = options.then((options) => options.map((option) => {
                        valueToLabelMap.set(option.value, option.label);
                        return option.value;
                    }));
                    return {
                        values,
                        formatValue: (value) => valueToLabelMap.get(value) ?? value,
                    };
                };
            }
        });
    }
    setupColumnCellRenderer({ col, colId, abColumn }) {
        this.setColDefProperty(col, 'cellRenderer', () => {
            const styledColumn = this.adaptableApi.styledColumnApi.getStyledColumnForColumnId(abColumn.columnId);
            if (styledColumn && !styledColumn.IsSuspended) {
                if (styledColumn.PercentBarStyle) {
                    return getPercentBarRendererForColumn(styledColumn, abColumn, this.adaptableApi);
                }
                if (styledColumn.BadgeStyle) {
                    return getBadgeRendererForColumn(styledColumn.BadgeStyle, abColumn, this.adaptableApi);
                }
                if (styledColumn.SparklineStyle) {
                    return 'agSparklineCellRenderer';
                }
            }
        });
        this.setColDefProperty(col, 'cellRendererParams', (userDefined) => {
            const styledColumn = this.adaptableApi.styledColumnApi.getStyledColumnForColumnId(abColumn.columnId);
            if (styledColumn && !styledColumn.IsSuspended) {
                if (styledColumn.SparklineStyle) {
                    const sanitizedSparklineOptions = AdaptableHelper.removeAdaptableObjectPrimitives(styledColumn.SparklineStyle.options);
                    const sparklineOptions = merge({}, userDefined?.sparklineOptions, sanitizedSparklineOptions);
                    return {
                        ...userDefined,
                        sparklineOptions,
                    };
                }
            }
        });
    }
    setupColumnTooltipValueGetter({ col, colId, abColumn }) {
        let hasTooptip = false;
        this.setColDefProperty(col, 'tooltipValueGetter', () => {
            const styledColumn = this.adaptableApi.styledColumnApi.getStyledColumnForColumnId(colId);
            if (styledColumn &&
                !styledColumn.IsSuspended &&
                styledColumn.PercentBarStyle &&
                styledColumn.PercentBarStyle.ToolTipText) {
                hasTooptip = true;
                if (styledColumn?.PercentBarStyle) {
                    return (params) => {
                        const min = this.adaptableApi.styledColumnApi.internalApi.getNumericStyleMinValue(styledColumn, abColumn, params.node, params.value);
                        const max = this.adaptableApi.styledColumnApi.internalApi.getNumericStyleMaxValue(styledColumn, abColumn, params.node, params.value);
                        const textOptions = styledColumn.PercentBarStyle.ToolTipText;
                        let returnValue = '';
                        if (textOptions.includes('CellValue')) {
                            returnValue = params.value;
                        }
                        if (textOptions.includes('PercentageValue')) {
                            const clampedValue = Helper.clamp(params.value, min, max);
                            const percentageValue = ((clampedValue - min) / (max - min)) * 100;
                            returnValue += ' ' + `(${percentageValue.toFixed(0)}%)`;
                        }
                        return returnValue ? returnValue : params.value;
                    };
                }
            }
        });
    }
    setupColumnQuickFilterText({ col, abColumn }) {
        this.setColDefProperty(col, 'getQuickFilterText', (userGetQuickFilterText) => {
            if (userGetQuickFilterText) {
                return userGetQuickFilterText;
            }
            return (params) => {
                const visibleCoulmnsMap = this.adaptableApi.layoutApi.getCurrentVisibleColumnIdsMapForTableLayout();
                const isVisible = visibleCoulmnsMap[abColumn.columnId];
                if (!isVisible) {
                    return '';
                }
                return this.adaptableApi.gridApi.getDisplayValueFromRowNode(params.node, abColumn.columnId);
            };
        });
    }
    setupColumnAllowedAggFuncs({ col, abColumn }) {
        this.setColDefProperty(col, 'allowedAggFuncs', () => {
            return abColumn.availableAggregationFunctions;
        });
    }
    setupColumnType(columnSetupInfo) {
        const { col, colId } = columnSetupInfo;
        // AG Grid introduced since v30.x an inferred cellDataType
        // the problem is that it breaks the default value formatter and/or editor (especially for Date columns)
        this.setColDefProperty(col, 'type', (original_columnType) => {
            const originalTypes = original_columnType == undefined
                ? []
                : Array.isArray(original_columnType)
                    ? original_columnType
                    : [original_columnType];
            const columnTypes = new Set(originalTypes);
            if (this.adaptableApi.columnApi.isCalculatedColumn(colId)) {
                columnTypes.add(CALCULATED_COLUMN_TYPE);
            }
            if (this.adaptableApi.columnApi.isFreeTextColumn(colId)) {
                columnTypes.add(FREE_TEXT_COLUMN_TYPE);
            }
            if (this.adaptableApi.columnApi.isActionColumn(colId)) {
                columnTypes.add(ACTION_COLUMN_TYPE);
            }
            if (this.adaptableApi.columnApi.isFdc3Column(colId)) {
                columnTypes.add(FDC3_COLUMN_TYPE);
            }
            return Array.from(columnTypes);
        });
    }
    setupColumnCellDataType(columnSetupInfo) {
        const { col } = columnSetupInfo;
        this.setColDefProperty(col, 'cellDataType', (original_cellDataType) => {
            return original_cellDataType ?? true;
        });
    }
    setupColumnHeader({ col, abColumn }) {
        const previousColumnHeader = col?.getColDef()?.headerName;
        this.setColDefProperty(col, 'headerName', (userHeaderName) => {
            // set the default to the AG Grid provided values
            // from https://github.com/ag-grid/ag-grid/blob/v26.1.0/community-modules/core/src/ts/columns/columnModel.ts#L2515
            let resultHeaderName = userHeaderName ?? StringExtensions.CamelCaseToHumanText(col.getColDef().field);
            const layoutCustomHeader = this.adaptableApi.layoutApi.getCurrentLayout().ColumnHeaders?.[col.getColId()];
            if (layoutCustomHeader) {
                resultHeaderName = layoutCustomHeader;
            }
            return resultHeaderName;
        });
        const newColumnHeader = col?.getColDef()?.headerName;
        return previousColumnHeader !== newColumnHeader;
    }
    setupColumnFilter({ col, colDef }) {
        this.setColDefProperty(col, 'filter', () => {
            if (!colDef.filter) {
                return;
            }
            if (!this.adaptableOptions.filterOptions.useAdaptableFiltering) {
                return;
            }
            this.agGridApi.destroyFilter(col);
            return FilterWrapperFactory(this.adaptableInstance);
        });
    }
    setupColumnFloatingFilterTemporarily(initialGridOptions) {
        initialGridOptions.columnDefs
            ?.filter((colDef) => !this.isColGroupDef(colDef))
            .map((colDef) => {
            const isFloatingFilterEnabled = initialGridOptions.defaultColDef?.floatingFilter || colDef.floatingFilter;
            if (isFloatingFilterEnabled) {
                colDef.floatingFilterComponent = FloatingFilterWrapperFactory(this.adaptableInstance);
            }
        });
    }
    setupColumnFloatingFilter({ col, colDef }) {
        const isFloatingFilterDisabled = !colDef.floatingFilter ||
            !this.adaptableOptions.filterOptions.useAdaptableFiltering ||
            !this.adaptableOptions.filterOptions.columnFilterOptions.showQuickFilter;
        this.setColDefProperty(col, 'floatingFilterComponent', () => {
            if (isFloatingFilterDisabled) {
                return;
            }
            return FloatingFilterWrapperFactory(this.adaptableInstance);
        });
        this.setColDefProperty(col, 'floatingFilter', (original_floatingFilter) => {
            if (isFloatingFilterDisabled) {
                return;
            }
            return FloatingFilterWrapperFactory(this.adaptableInstance);
        });
        this.setColDefProperty(col, 'suppressFloatingFilterButton', () => {
            return !isFloatingFilterDisabled;
        });
    }
    setupColumnValueFormatter({ col, abColumn }) {
        this.setColDefProperty(col, 'valueFormatter', (userPropertyValue) => {
            const activeFormatColumnsWithDisplayFormat = this.adaptableApi.formatColumnApi.internalApi.getFormatColumnsWithDisplayFormatForColumn(abColumn);
            if (!activeFormatColumnsWithDisplayFormat.length) {
                return;
            }
            return (params) => {
                const { node, value } = params;
                const mostRelevantFormatColumn = this.adaptableApi.formatColumnApi.internalApi.getMostRelevantFormatColumnForColumn(activeFormatColumnsWithDisplayFormat, abColumn, { node, value });
                if (!mostRelevantFormatColumn) {
                    // ALL FormatColumns are conditional and NONE of them are relevant for this row
                    return value;
                }
                const formatterOptions = mostRelevantFormatColumn.DisplayFormat.Options;
                if (mostRelevantFormatColumn.DisplayFormat.Formatter === 'NumberFormatter') {
                    // change the Number format - if the scope allows it
                    if (this.adaptableApi.columnScopeApi.isColumnInNumericScope(abColumn, mostRelevantFormatColumn.Scope)) {
                        let cellValue = params.value;
                        if (typeof params.value?.toNumber === 'function' &&
                            typeof params.value?.toString === 'function') {
                            // aggregation values are wrapped in an AG Grid specific object
                            cellValue = params.value.toNumber();
                        }
                        return this.adaptableApi.formatColumnApi.internalApi.getNumberFormattedValue(cellValue, params.node, abColumn, formatterOptions);
                    }
                }
                if (mostRelevantFormatColumn.DisplayFormat.Formatter === 'DateFormatter') {
                    // change the Date format - if the scope allows it
                    if (this.adaptableApi.columnScopeApi.isColumnInDateScope(abColumn, mostRelevantFormatColumn.Scope)) {
                        let cellValue = params.value;
                        if (typeof params.value?.toNumber === 'function' &&
                            typeof params.value?.toString === 'function') {
                            // aggregation values are wrapped in an AG Grid specific object
                            cellValue = params.value.toString();
                        }
                        return this.adaptableApi.formatColumnApi.internalApi.getDateFormattedValue(cellValue, params.node, abColumn, formatterOptions);
                    }
                }
                if (mostRelevantFormatColumn.DisplayFormat.Formatter === 'StringFormatter') {
                    // change the String format - if the scope allows it
                    if (this.adaptableApi.columnScopeApi.isColumnInTextScope(abColumn, mostRelevantFormatColumn.Scope)) {
                        let cellValue = params.value;
                        if (typeof params.value?.toNumber === 'function' &&
                            typeof params.value?.toString === 'function') {
                            // aggregation values are wrapped in an AG Grid specific object
                            cellValue = params.value.toString();
                        }
                        return this.adaptableApi.formatColumnApi.internalApi.getStringFormattedValue(cellValue, params.node, abColumn, formatterOptions);
                    }
                }
                // should NEVER arrive at this line, but just to be sure
                return value;
            };
        });
    }
    setupColumnEditable({ col }) {
        this.setColDefProperty(col, 'editable', (original_editable) => {
            // cell is NOT editable by default
            const editableCallback = (params) => {
                const getOriginalColDefEditable = () => {
                    if (typeof original_editable === 'function') {
                        return original_editable(params);
                    }
                    else {
                        return original_editable;
                    }
                };
                // 1. evaluate EditOptions.isCellEditable if provided
                if (this.adaptableApi.gridApi.internalApi.hasCellEditableAccordingToEditOptions()) {
                    const gridCell = this.adaptableApi.gridApi.getGridCellFromRowNode(params.node, params.column.getColId());
                    const editOptionsEditability = this.adaptableApi.gridApi.internalApi.isCellEditableAccordingToEditOptions(gridCell, getOriginalColDefEditable());
                    if (editOptionsEditability) {
                        return editOptionsEditability;
                    }
                }
                // 2. otherwise, fallback to colDef.editable
                return getOriginalColDefEditable();
            };
            return editableCallback;
        });
    }
    setupColumnValueSetter({ col, colId, abColumn }) {
        this.setColDefProperty(col, 'valueSetter', (userValueSetter) => {
            const preventEditAlertsForColumn = this.adaptableApi.alertApi.internalApi
                .getAlertDefinitionsWithPreventEdit()
                .filter((alertDefinition) => {
                return this.adaptableApi.columnScopeApi.isColumnInScope(abColumn, alertDefinition.Scope);
            });
            const noValidations = !preventEditAlertsForColumn.length && !this.adaptableOptions.editOptions?.validateOnServer;
            if (noValidations) {
                return;
            }
            const valueSetter = (params) => {
                const field = params.column.getColDef().field;
                if (noValidations) {
                    //TODO also consider the case when userValueSetter is a string
                    if (typeof userValueSetter === 'function') {
                        return userValueSetter(params);
                    }
                    // we allowed it go reach this point
                    // just to run isCellEditable
                    // and since this has already run and we have no other validations
                    // just assign the new value and exit
                    if (field) {
                        params.data[field] = params.newValue;
                    }
                    return true;
                }
                const cellDataChangedInfo = this.adaptableApi.internalApi.buildCellDataChangedInfo({
                    oldValue: params.oldValue,
                    newValue: params.newValue,
                    column: this.adaptableApi.columnApi.getColumnWithColumnId(params.column.getColId()),
                    primaryKeyValue: this.adaptableApi.gridApi.getPrimaryKeyValueForRowNode(params.node),
                    rowNode: params.node,
                    trigger: 'edit',
                });
                if (cellDataChangedInfo.oldValue === cellDataChangedInfo.newValue) {
                    return true;
                }
                /**
                 * Validate on the future row, with the new value.
                 * structuredClone fails, it contains functions.
                 */
                const newRow = { ...params.node, data: { ...params.node.data } };
                newRow.data[field] = params.newValue;
                const cellDataChangeInfoForSyncValidation = {
                    ...cellDataChangedInfo,
                    rowNode: newRow,
                };
                if (!this.adaptableApi.internalApi
                    .getValidationService()
                    .performValidation(cellDataChangeInfoForSyncValidation)) {
                    return false;
                }
                const onServerValidationCompleted = () => { };
                if (this.adaptableOptions.editOptions?.validateOnServer) {
                    this.adaptableApi.internalApi
                        .getValidationService()
                        .performServerValidation(cellDataChangedInfo, {
                        onServerValidationCompleted,
                    })();
                }
                //TODO also consider the case when userValueSetter is a string
                if (typeof userValueSetter === 'function') {
                    return userValueSetter(params);
                }
                if (field) {
                    params.data[field] = params.newValue;
                }
                else {
                    throw `Cannot edit a column without a field - column id was ${colId}`;
                }
                return true;
            };
            return valueSetter;
        });
    }
    setupColumnComparator({ col, colId, abColumn }) {
        const customSort = this.adaptableApi.customSortApi.getCustomSortForColumn(colId);
        const columnSortComparer = this.adaptableApi.customSortApi.internalApi.getCustomSortComparer(abColumn.columnId);
        const comparatorGetter = (propName) => {
            return () => {
                return this.adaptableApi.columnApi.internalApi.getActiveColumnComparator(colId, customSort, columnSortComparer);
            };
        };
        this.setColDefProperty(col, 'comparator', comparatorGetter('comparator'));
        this.setColDefProperty(col, 'pivotComparator', comparatorGetter('pivotComparator'));
    }
    isQuickSearchActive(gridCell, params) {
        const quickSearchValue = this.adaptableApi.quickSearchApi.getQuickSearchValue();
        if (StringExtensions.IsNullOrEmpty(quickSearchValue)) {
            return false;
        }
        if (!gridCell.rowNode) {
            return false;
        }
        let quickSearchContext;
        const isCellSearchable = this.adaptableOptions.quickSearchOptions.isCellSearchable;
        if (isCellSearchable) {
            quickSearchContext = {
                ...this.adaptableApi.internalApi.buildBaseContext(),
                gridCell,
                quickSearchValue,
            };
            if (!isCellSearchable(quickSearchContext)) {
                return false;
            }
        }
        const runCustomQuickSearch = this.adaptableOptions.quickSearchOptions.runCustomQuickSearch;
        if (runCustomQuickSearch) {
            if (!quickSearchContext) {
                quickSearchContext = {
                    ...this.adaptableApi.internalApi.buildBaseContext(),
                    gridCell,
                    quickSearchValue,
                };
            }
            return runCustomQuickSearch(quickSearchContext);
        }
        const isCaseSensitive = this.adaptableOptions.quickSearchOptions.isQuickSearchCaseSensitive;
        const cellDisplayValue = String(gridCell.displayValue);
        const displayValue = isCaseSensitive ? cellDisplayValue : cellDisplayValue.toLocaleLowerCase();
        const searchText = isCaseSensitive ? quickSearchValue : quickSearchValue.toLocaleLowerCase();
        return displayValue.indexOf(searchText) !== -1;
    }
    getEditableCellClass(gridCell, params) {
        const editableCellStyle = this.adaptableApi.userInterfaceApi.getEditableCellStyle();
        if (!editableCellStyle?.ClassName) {
            return null;
        }
        const isCellEditable = this.adaptableApi.gridApi.isCellEditable(gridCell);
        return isCellEditable ? editableCellStyle.ClassName : null;
    }
    getReadonlyCellClass(gridCell, params) {
        const readonlyCellStyle = this.adaptableApi.userInterfaceApi.getReadOnlyCellStyle();
        if (!readonlyCellStyle?.ClassName) {
            return null;
        }
        const isCellReadonly = !this.adaptableApi.gridApi.isCellEditable(gridCell);
        return isCellReadonly ? readonlyCellStyle.ClassName : null;
    }
    getEditedCellClass(gridCell, params) {
        const editedCellStyle = this.adaptableApi.userInterfaceApi.getEditedCellStyle();
        if (!editedCellStyle?.ClassName) {
            return null;
        }
        const isCellEdited = this.adaptableApi.gridApi.isCellEdited(gridCell);
        return isCellEdited ? editedCellStyle.ClassName : null;
    }
    getAlertCellClass(gridCell, params) {
        const alert = this.adaptableApi.alertApi.internalApi.getAdaptableAlertWithHighlightCell(gridCell.column.columnId, params.node);
        const highlightCell = alert?.alertDefinition?.AlertProperties?.HighlightCell;
        return typeof highlightCell === 'object' && highlightCell?.ClassName
            ? highlightCell?.ClassName
            : null;
    }
    getFlashingCellClass(gridcell, params) {
        const primaryKey = params.node.aggData ? params.node.id : gridcell.primaryKeyValue;
        const flashingCell = this.adaptableApi.flashingCellApi.internalApi.getAdaptableFlashingCellFor(primaryKey, gridcell.column.columnId);
        if (!flashingCell) {
            return;
        }
        return flashingCell.direction === 'up'
            ? flashingCell.flashingCellDefinition.UpChangeStyle?.ClassName
            : flashingCell.direction === 'down'
                ? flashingCell.flashingCellDefinition.DownChangeStyle?.ClassName
                : flashingCell.direction === 'neutral'
                    ? flashingCell.flashingCellDefinition.NeutralChangeStyle?.ClassName
                    : undefined;
    }
    getNoteCellClassName(gridCell, params) {
        if (!this.adaptableApi.internalApi.getModuleService().isModuleAvailable('Note')) {
            return;
        }
        if (!this.adaptableApi.noteApi.internalApi.areNotesSupported()) {
            return;
        }
        const cellPosition = {
            PrimaryKeyValue: gridCell.primaryKeyValue,
            ColumnId: gridCell.column.columnId,
        };
        const cellNote = this.adaptableApi.noteApi.getNoteForCell(cellPosition);
        if (!cellNote) {
            return undefined;
        }
        return 'ab-Cell-Note';
    }
    getCommentCellClassName(gridCell, params) {
        if (!this.adaptableApi.internalApi.getModuleService().isModuleAvailable('Comment')) {
            return;
        }
        if (!this.adaptableApi.commentApi.internalApi.areCommentsSupportedInLayout()) {
            return;
        }
        const position = {
            PrimaryKeyValue: gridCell.primaryKeyValue,
            ColumnId: gridCell.column.columnId,
        };
        const cellComments = this.adaptableApi.commentApi.getCommentThreadForCell(position);
        if (!cellComments) {
            return undefined;
        }
        return 'ab-Cell-Comment';
    }
    getFormatColumnCellClass(formatColumns, abColumn, params) {
        const classNames = formatColumns
            .map((formatColumn) => {
            if (formatColumn.Style?.ClassName &&
                this.adaptableApi.formatColumnApi.internalApi.formatColumnShouldRender(formatColumn, abColumn, params.node, params.value)) {
                return formatColumn.Style?.ClassName;
            }
        })
            .filter((x) => !!x);
        return classNames;
    }
    getQuickSearchCellStyle() {
        const quickSearchStyle = this.adaptableApi.quickSearchApi.getQuickSearchStyle();
        if (!quickSearchStyle || StringExtensions.IsNotNullOrEmpty(quickSearchStyle.ClassName)) {
            return undefined;
        }
        return convertAdaptableStyleToCSS(quickSearchStyle);
    }
    getReadOnlyCellStyle(gridCell, params) {
        const editableCellStyle = this.adaptableApi.userInterfaceApi.getReadOnlyCellStyle();
        if (!editableCellStyle) {
            return undefined;
        }
        if (gridCell) {
            if (!this.adaptableApi.gridApi.isCellEditable(gridCell)) {
                return convertAdaptableStyleToCSS(editableCellStyle);
            }
        }
        return undefined;
    }
    getEditableCellStyle(gridCell, params) {
        const editableCellStyle = this.adaptableApi.userInterfaceApi.getEditableCellStyle();
        if (!editableCellStyle) {
            return undefined;
        }
        if (gridCell) {
            if (this.adaptableApi.gridApi.isCellEditable(gridCell)) {
                return convertAdaptableStyleToCSS(editableCellStyle);
            }
        }
        return undefined;
    }
    getEditedCellStyle(gridCell, params) {
        const editedCellStyle = this.adaptableApi.userInterfaceApi.getEditedCellStyle();
        if (!editedCellStyle) {
            return undefined;
        }
        if (gridCell) {
            if (this.adaptableApi.gridApi.isCellEdited(gridCell)) {
                return convertAdaptableStyleToCSS(editedCellStyle);
            }
        }
        return undefined;
    }
    /**
     * The combination of styled column and format cells
     * This functiond decides when the two can be merged.
     */
    getFormatColumnAndStyledColumnCellStyle(gridCell, params) {
        let styledColumn = this.adaptableApi.styledColumnApi.getStyledColumnForColumnId(gridCell.column.columnId);
        let styledColumnStyle = {};
        if (styledColumn && !styledColumn?.IsSuspended) {
            const styledCellStyle = this.getStyledColumnStyle(styledColumn, gridCell.column, params);
            // for percentbar we want to merge
            if (styledColumn.PercentBarStyle || styledColumn.BadgeStyle || styledColumn.GradientStyle) {
                styledColumnStyle = styledCellStyle;
            }
            else {
                // For other ones wo do not want to merge
                return styledCellStyle;
            }
        }
        const activeFormatColumnsWithStyle = this.adaptableApi.formatColumnApi.internalApi.getFormatColumnsWithStyleForColumn(gridCell.column);
        // background color of styledColumn.GradientStyle has precedence and wins over FormatColumn
        return {
            ...this.getFormatColumnCellStyle(gridCell.column, activeFormatColumnsWithStyle, params),
            ...styledColumnStyle,
        };
    }
    getStyledColumnStyle(styledColumn, abColumn, params) {
        let style = {};
        const gradientStyle = styledColumn?.GradientStyle;
        if (params.value === undefined) {
            return;
        }
        let colValue = params.value;
        if (this.adaptableApi.gridApi.isGroupRowNode(params.node)) {
            return style;
            // if (styledColumn.IncludeGroupedRows) {
            //   const minColumnValue =
            //     this.adaptableApi.styledColumnApi.internalApi.getMinValueForNumericColumn(abColumn);
            //   const maxColumnValue =
            //     this.adaptableApi.styledColumnApi.internalApi.getMaxValueForNumericColumn(abColumn);
            //   /**
            //    * Color should always be in bounds, it should not overflow.
            //    * If the value is out of range, it shoul set the maximum/minimum color.
            //    */
            //   colValue = clamp(params.value, minColumnValue, maxColumnValue);
            // } else {
            //   return style;
            // }
        }
        if (styledColumn.BadgeStyle &&
            !styledColumn.BadgeStyle.RowScope?.ExcludeSummaryRows &&
            this.adaptableApi.gridApi.isSummaryNode(params?.node)) {
            return style;
        }
        if (gradientStyle) {
            const min = this.adaptableApi.styledColumnApi.internalApi.getNumericStyleMinValue(styledColumn, abColumn, params.node, colValue);
            const max = this.adaptableApi.styledColumnApi.internalApi.getNumericStyleMaxValue(styledColumn, abColumn, params.node, colValue);
            let cellBackColor;
            let reverseGradient = false;
            if (gradientStyle.ColumnComparison) {
                cellBackColor = gradientStyle.ColumnComparison.Color;
            }
            else {
                const matchingRange = this.adaptableApi.styledColumnApi.internalApi.findRangeForColumn(gradientStyle.CellRanges, abColumn, gradientStyle.RangeValueType, colValue);
                if (matchingRange) {
                    cellBackColor = matchingRange.Color;
                    reverseGradient = matchingRange.ReverseGradient;
                }
            }
            const increase = Math.abs(max - min);
            const percentage = ((colValue - min) / increase) * 100;
            let alpha = Number((percentage / 100).toPrecision(2));
            if (reverseGradient) {
                alpha = 1 - alpha;
            }
            const preparedColor = getVariableColor(cellBackColor);
            // if no range match, do not apply color (black in this case)
            if (cellBackColor) {
                style.backgroundColor = tinycolor(preparedColor).setAlpha(alpha).toRgbString();
            }
        }
        if (styledColumn.PercentBarStyle && styledColumn.PercentBarStyle.CellText) {
            style.paddingTop = 0;
            style.paddingBottom = 0;
        }
        return style;
    }
    getFormatColumnCellStyle(abColumn, activeFormatColumnsWithStyle, params) {
        if (!activeFormatColumnsWithStyle.length) {
            return {};
        }
        const relevantFormatColumnsWithStyle = activeFormatColumnsWithStyle.filter((formatColumn) => {
            return this.adaptableApi.formatColumnApi.internalApi.formatColumnShouldRender(formatColumn, abColumn, params.node, params.value);
        });
        return this.getFormatColumnAdaptableStyle(relevantFormatColumnsWithStyle);
    }
    getFormatColumnAdaptableStyle(formatColumns) {
        // first has more precedence, then they need to be applied in reverse order
        return formatColumns.reduceRight((style, formatColumn) => {
            const formatColumnStyle = formatColumn.Style
                ? convertAdaptableStyleToCSS(formatColumn.Style)
                : {};
            if (formatColumn.CellAlignment) {
                switch (formatColumn.CellAlignment) {
                    case 'Left':
                        style.textAlign = 'left';
                        break;
                    case 'Right':
                        style.textAlign = 'right';
                        break;
                    case 'Center':
                        style.textAlign = 'center';
                        break;
                }
            }
            return { ...style, ...formatColumnStyle };
        }, {});
    }
    getAlertCellStyle(gridCell, params) {
        const alert = this.adaptableApi.alertApi.internalApi.getAdaptableAlertWithHighlightCell(gridCell.column.columnId, params.node);
        if (alert) {
            const highlightCell = alert.alertDefinition.AlertProperties.HighlightCell;
            if (typeof highlightCell === 'object') {
                return convertAdaptableStyleToCSS(highlightCell);
            }
            return {
                backgroundColor: UIHelper.getColorByMessageType(alert.alertDefinition.MessageType),
            };
        }
    }
    getFlashingCellStyle(gridCell, params) {
        const primaryKey = params.node.aggData ? params.node.id : gridCell.primaryKeyValue;
        const flashingCell = this.adaptableApi.flashingCellApi.internalApi.getAdaptableFlashingCellFor(primaryKey, gridCell.column.columnId);
        if (!flashingCell) {
            return {};
        }
        return convertAdaptableStyleToCSS((flashingCell.direction === 'up'
            ? flashingCell.flashingCellDefinition.UpChangeStyle
            : flashingCell.direction === 'down'
                ? flashingCell.flashingCellDefinition.DownChangeStyle
                : flashingCell.flashingCellDefinition.NeutralChangeStyle) ?? {});
    }
    getCellHighlightStyle(gridCell, params) {
        const cellHightlight = this.adaptableApi.internalApi
            .getInternalState()
            .CellHighlightInfo.find((cellHighlightInfo) => {
            return (gridCell.column.columnId === cellHighlightInfo.columnId &&
                cellHighlightInfo.primaryKeyValue === gridCell.primaryKeyValue);
        });
        if (cellHightlight) {
            return convertAdaptableStyleToCSS(cellHightlight.highlightStyle);
        }
    }
    isColGroupDef(columnDefinition) {
        // @ts-ignore
        return columnDefinition['children'] != null;
    }
}
