"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FormatColumnInternalApi = void 0;
const tslib_1 = require("tslib");
const ApiBase_1 = require("../Implementation/ApiBase");
const StringExtensions_1 = tslib_1.__importDefault(require("../../Utilities/Extensions/StringExtensions"));
const FormatHelper_1 = tslib_1.__importDefault(require("../../Utilities/Helpers/FormatHelper"));
const ObjectFactory_1 = tslib_1.__importDefault(require("../../Utilities/ObjectFactory"));
const ModuleConstants = tslib_1.__importStar(require("../../Utilities/Constants/ModuleConstants"));
const AdaptableLogger_1 = require("../../agGrid/AdaptableLogger");
function getParentGroup(source) {
    if (!source) {
        return undefined;
    }
    let current = source;
    while (current.getParent()) {
        current = current.getParent();
        if (!current) {
            return undefined;
        }
        const currentProvidedColumnGroup = current.getProvidedColumnGroup();
        // for columns that don't belong to groups or belong to a group that doesn't have full depth
        // there are artificial parent groups created by ag-grid
        // so for the current column group, lets grab the leaf columns
        // and if the only leaf column is the source column, then we can continue
        // and do another iteration where we go up one level
        if (currentProvidedColumnGroup) {
            const leafColumns = current.getLeafColumns();
            if (leafColumns.length === 1 && leafColumns[0] === source) {
                continue;
            }
            // otherwise, we have found the parent group corresponding to the source column
            return currentProvidedColumnGroup;
        }
    }
    return undefined;
}
class FormatColumnInternalApi extends ApiBase_1.ApiBase {
    /**
     * Retrieves all Format Columns in Adaptable State with the `Style` property set
     * @returns format columns
     */
    getAllFormatColumnWithStyle(config) {
        return this.getFormatColumnApi()
            .getFormatColumns(config)
            .filter((fc) => fc.Style);
    }
    /**
     * Retrieves all Format Columns in Adaptable State with the `Style` or the `CellAlignment` property set
     * @returns format columns
     */
    getAllFormatColumnWithStyleAndCellAlignment(config) {
        return this.getFormatColumnApi()
            .getFormatColumns(config)
            .filter((fc) => fc.Style || fc.CellAlignment);
    }
    /**
     * Retrieves all Format Columns in Adaptable State with `DisplayFormat` property set
     * @returns format columns
     */
    getAllFormatColumnWithDisplayFormat(config) {
        return this.getFormatColumnApi()
            .getFormatColumns(config)
            .filter((fc) => fc.DisplayFormat);
    }
    /**
     * Retrieves all Format Columns in Adaptable State with `CellAlignment` property set
     * @returns format columns
     */
    getAllFormatColumnWithCellAlignment(config) {
        return this.getFormatColumnApi()
            .getFormatColumns(config)
            .filter((fc) => fc.CellAlignment);
    }
    /**
     * Get all FormatColumns which are defined for this column and have a custom AdaptableStyle
     * @param column
     * @param config
     * @returns list of FormatColumn
     */
    getFormatColumnsWithStyleForColumn(column, config) {
        const formatColumns = this.getAllFormatColumnWithStyleAndCellAlignment()
            .filter((formatColumn) => {
            // FormatColumn default target is 'cell', so if no target is specified, we assume 'cell'
            const fcTarget = formatColumn.Target ?? 'cell';
            return fcTarget === config.target;
        })
            .filter((formatColumn) => config?.includeSuspended || !formatColumn.IsSuspended);
        return this.getFormatColumnWithColumnInScope(formatColumns, column);
    }
    /**
     * Gets Format Column if any for given Column which includes Style element with ClassName
     * @param column The Column for which to retrieve the Format Column
     * @returns format columns
     */
    getFormatColumnWithStyleClassNameForColumn(column, config) {
        return this.getFormatColumnsWithStyleForColumn(column, config).filter((formatColumn) => StringExtensions_1.default.IsNotNullOrEmpty(formatColumn?.Style?.ClassName));
    }
    getFormatColumnWithColumnInScope(formatColumns, column) {
        return this.getFormatColumnInColumnScope(formatColumns).filter((scopedFormatColumn) => this.getColumnScopeApi().isColumnInScope(column, scopedFormatColumn.Scope));
    }
    // TODO is this really needed, I don't think it achieves anything
    getFormatColumnInColumnScope(formatColumns) {
        // we need to maintain the format columns order, therefore we will extract all 3 scope types in a single iteration
        return formatColumns.filter((fc) => {
            return (
                // this.getFormatColumnsWithColumnScope(formatColumns)
                (this.getColumnScopeApi().scopeHasColumns(fc.Scope) ||
                    // this.getFormatColumnsWithDataTypeScope(formatColumns)
                    this.getColumnScopeApi().scopeHasDataType(fc.Scope) ||
                    // this.getFormatColumnsWithAllScope(formatColumns)
                    this.getColumnScopeApi().scopeIsAll(fc.Scope) || this.getColumnScopeApi().scopeHasColumnType(fc.Scope))
            );
        });
    }
    /**
     * Get all FormatColumns which are defined for this column and have a custom DisplayFormat
     * @param column
     * @param config
     * @returns list of FormatColumn
     */
    getFormatColumnsWithDisplayFormatForColumn(column, config) {
        const formatColumns = this.getAllFormatColumnWithDisplayFormat()
            .filter((formatColumn) => {
            // FormatColumn default target is 'cell', so if no target is specified, we assume 'cell'
            const fcTarget = formatColumn.Target ?? 'cell';
            return fcTarget === config.target;
        })
            .filter((formatColumn) => config?.includeSuspended || !formatColumn.IsSuspended);
        return this.getFormatColumnWithColumnInScope(formatColumns, column);
    }
    /**
     * Format value according to format options.
     *
     * @param customDisplayFormatterContext context that includes value to format
     * @param options formatter options
     */
    getNumberFormattedValue(value, node, column, options) {
        const preparedValue = this.applyCustomFormatters(value, node, column, options);
        return FormatHelper_1.default.NumberFormatter(preparedValue, options, node, column, this.getAdaptableApi());
    }
    /**
     * Format value according to format options.
     *
     * @param value  context that includes value to format
     * @param options formatter options
     */
    getStringFormattedValue(value, node, column, options) {
        const preparedValue = this.applyCustomFormatters(value, node, column, options);
        return FormatHelper_1.default.StringFormatter(preparedValue, options, node, column, this.getAdaptableApi());
    }
    /**
     * Format value according to format options.
     *
     * @param customDisplayFormatterContext  context that includes value to format
     * @param options formatter options
     */
    getDateFormattedValue(value, node, abColumn, options) {
        const preparedValue = this.applyCustomFormatters(value, node, abColumn, options);
        const dateFormatterOptions = options;
        // only use DateFormatter if we have a pattern
        return dateFormatterOptions.Pattern
            ? FormatHelper_1.default.DateFormatter(preparedValue, options)
            : preparedValue;
    }
    applyCustomFormatters(value, node, abColumn, options) {
        const columnCustomFormatters = options?.CustomDisplayFormats ?? [];
        if (!columnCustomFormatters?.length) {
            return value;
        }
        const customFormattersFromOptions = this.getFormatColumnOptions()?.customDisplayFormatters ?? [];
        // formatters are applied in the order they are defined in the options
        const customFormatters = customFormattersFromOptions.filter((customFormatterOption) => columnCustomFormatters.includes(customFormatterOption.id));
        const customDisplayFormatterContext = ObjectFactory_1.default.CreateCustomDisplayFormatterContext(value, node, abColumn, this.getAdaptableApi());
        return customFormatters.reduce((context, formatter) => {
            if (formatter && formatter.handler) {
                return formatter.handler(context);
            }
            return context.cellValue;
        }, customDisplayFormatterContext);
    }
    /**
     * Returns all Predicates appropriate for the given Scope
     * @param scope Scope to check
     */
    getFormatColumnDefsForScope(scope) {
        return this.getAdaptableApi()
            .predicateApi.internalApi.getFormatColumnPredicateDefs(scope)
            .filter((predicateDef) => this.getColumnScopeApi().isScopeInScope(scope, predicateDef.columnScope));
    }
    formatColumnWithColumnGroupScopeShouldRender(formatColumn, column) {
        if (!formatColumn.ColumnGroupScope) {
            return true;
        }
        const agGridApi = this.getAdaptableApi().agGridApi;
        const agGridColumn = this._adaptable.isInPivotMode()
            ? agGridApi.getPivotResultColumns().find((col) => col.getColId() === column.columnId)
            : agGridApi.getColumn(column.columnId);
        if (!agGridColumn) {
            return false;
        }
        const columnGroupParentForCurrentColumn = getParentGroup(agGridColumn);
        if (!columnGroupParentForCurrentColumn) {
            return false;
        }
        const columnGroupScope = columnGroupParentForCurrentColumn.isExpanded()
            ? 'Expanded'
            : 'Collapsed';
        const columnGroupLeafColumns = columnGroupParentForCurrentColumn.getLeafColumns();
        const columnIsTopLevel = columnGroupParentForCurrentColumn.getLevel() === 0 &&
            columnGroupLeafColumns.length === 1 &&
            columnGroupLeafColumns[0] === agGridColumn;
        if (columnIsTopLevel) {
            // for top-level columns don't apply formatColumns that have an explicit ColumnGroupScope defined
            return false;
        }
        if (formatColumn.ColumnGroupScope === 'Both') {
            return true;
        }
        return formatColumn.ColumnGroupScope === columnGroupScope;
    }
    /**
     * Checks if format column is relevant for a given cell (intersection of given AdaptableColumn and RowNode)
     *
     * @param formatColumn
     * @param column
     * @param params
     */
    formatColumnShouldRenderInCell(formatColumn, column, rowNode, cellValue, pivotResultColumn) {
        // suspended is important to be first
        if (formatColumn.IsSuspended) {
            return false;
        }
        const isSummaryNode = this.getGridApi().isSummaryNode(rowNode);
        const isGroupedRowNode = this.getGridApi().isGroupRowNode(rowNode);
        const isGrandTotalRowNode = this.getGridApi().isGrandTotalRowNode(rowNode);
        if (isGrandTotalRowNode) {
            if (formatColumn.RowScope?.ExcludeTotalRows) {
                return false;
            }
        }
        else if (isSummaryNode) {
            if (formatColumn.RowScope?.ExcludeSummaryRows) {
                return false;
            }
        }
        else if (isGroupedRowNode) {
            if (formatColumn.RowScope?.ExcludeGroupRows) {
                return false;
            }
        }
        else {
            if (formatColumn.RowScope?.ExcludeDataRows) {
                return false;
            }
        }
        if (formatColumn.ColumnGroupScope &&
            !this.formatColumnWithColumnGroupScopeShouldRender(formatColumn, column)) {
            return false;
        }
        if (!formatColumn.Rule) {
            return true;
        }
        // first run the predicate
        if (formatColumn.Rule.Predicates && formatColumn.Rule?.Predicates?.length) {
            const predicateDefHandlerContext = {
                value: cellValue,
                oldValue: null,
                displayValue: cellValue,
                node: rowNode,
                column: column,
                ...this.getAdaptableInternalApi().buildBaseContext(),
            };
            return this.evaluatePredicate(formatColumn, predicateDefHandlerContext);
        } // then run the Expression
        else if (formatColumn.Rule.BooleanExpression) {
            return this.evaluateExpression(formatColumn, rowNode, pivotResultColumn);
        }
        // nothing has passed then return false
        return false;
    }
    /**
     * Checks if format column is relevant for a given Column Header
     *
     * @param formatColumn
     * @param column
     */
    formatColumnShouldRenderInHeader(formatColumn, column) {
        if (formatColumn.IsSuspended) {
            return false;
        }
        if (formatColumn.ColumnGroupScope &&
            !this.getFormatColumnApi().internalApi.formatColumnWithColumnGroupScopeShouldRender(formatColumn, column)) {
            return false;
        }
        return true;
    }
    evaluatePredicate(formatColumn, predicateDefHandlerContext) {
        return this.getPredicateApi().handleColumnPredicates(formatColumn.Rule?.Predicates, predicateDefHandlerContext, false);
    }
    evaluateExpression(formatColumn, node, pivotResultColumn) {
        const isValidExpression = this.getExpressionApi().isValidBooleanExpression(formatColumn.Rule.BooleanExpression, ModuleConstants.FormatColumnModuleId, `Invalid format column rule '${formatColumn.Rule.BooleanExpression}'`);
        try {
            return (isValidExpression &&
                this.getAdaptableApi()
                    .internalApi.getQueryLanguageService()
                    .evaluateBooleanExpression(formatColumn.Rule.BooleanExpression, ModuleConstants.FormatColumnModuleId, node, { pivotResultColumn }));
        }
        catch (error) {
            (0, AdaptableLogger_1.errorOnce)(error.message);
            return false;
        }
    }
    /**
     * Extract from the given FormatColumns the one which is the most relevant  for a given cell (intersection of given AdaptableColumn and RowNode)
     *
     * @param formatColumns
     * @param column
     * @param params
     */
    getMostRelevantFormatColumnForColumn(formatColumns, column, params) {
        return formatColumns.find((formatColumn) => this.formatColumnShouldRenderInCell(formatColumn, column, params.node, params.value));
    }
    /**
     * Retrieves all Format Columns which have an Expression
     * @returns Format Columns with Expression
     */
    getFormatColumnsWithExpression(config) {
        return this.getFormatColumnApi()
            .getFormatColumns(config)
            .filter((fc) => !!fc.Rule?.BooleanExpression);
    }
    getFormatColumnsDependentOnColumns(columnSet, config) {
        return this.getFormatColumnApi()
            .getFormatColumns(config)
            .filter((fc) => fc.Rule?.Predicates?.some((p) => columnSet.has(p.ColumnId)));
    }
    /**
     * Retrieves the columns that need rerendering based on format column predicates.
     */
    getFormatColumnColumnsDependentOnColumnChange(column) {
        const impactedColumnIds = new Set();
        impactedColumnIds.add(column.columnId);
        this.getCalculatedColumnApi()
            .internalApi.getCalculatedColumnsDependentOnColumn(column)
            .forEach((calculatedColumnId) => {
            impactedColumnIds.add(calculatedColumnId);
        });
        const columnsThatNeedRefresh = new Set();
        this.getFormatColumnsDependentOnColumns(impactedColumnIds).forEach((formatColumn) => {
            this.getAdaptableApi()
                .columnScopeApi.getColumnsInScope(formatColumn.Scope)
                .forEach((col) => {
                columnsThatNeedRefresh.add(col.columnId);
            });
        });
        return [...columnsThatNeedRefresh];
    }
    formatColumnHeaderName(headerName, params) {
        // currently we only format the header name for normal columns (NOT column groups)
        if (!params.column) {
            return headerName;
        }
        const abColumn = this.getColumnApi().getColumnWithColumnId(params.column.getColId());
        const activeFormatColumnsWithDisplayFormat = this.getFormatColumnApi().internalApi.getFormatColumnsWithDisplayFormatForColumn(abColumn, {
            target: 'columnHeader',
        });
        if (!activeFormatColumnsWithDisplayFormat.length) {
            return headerName;
        }
        const mostRelevantFormatColumn = activeFormatColumnsWithDisplayFormat.find((formatColumn) => this.formatColumnShouldRenderInHeader(formatColumn, abColumn));
        if (!mostRelevantFormatColumn) {
            return headerName;
        }
        if (mostRelevantFormatColumn.DisplayFormat?.Formatter !== 'StringFormatter') {
            // headers are always strings, so we can only use StringFormatter
            return headerName;
        }
        const formattedHeaderName = this.getFormatColumnApi().internalApi.getStringFormattedValue(headerName, null, abColumn, mostRelevantFormatColumn.DisplayFormat.Options);
        return formattedHeaderName;
    }
}
exports.FormatColumnInternalApi = FormatColumnInternalApi;
